1.
그동안,
JSP를 사용해서, MVC 패턴을 구현 할 때, View 와 Controller 파트를 작성 했다.
하지만,
Controller 파트는 .jsp 파일 에서 구현하는게 아니라 .java 파일에서 구현 하는것이 맞다.
(Controller.jsp 파일 내부에 작성된 코드를 보면, 모두 JAVA 문법으로 작성 했다.)
그런데,
.java 파일 이라고 해서 class 파일로 생성 하는게 맞을까 ? (아니다.)
.jsp 파일을 컴파일 하면, 서블릿 (Servlet) .java 파일이 된다 !
(컴파일 == ctrl + f11 해서 자바를 실행 하는 경우)
즉, .jsp 파일은 컴파일이 되면 JAVA 파일이 된다는 의미 이다.
따라서,
class가 아닌, 서블릿 으로 Controller 파트를 구현 해야 한다.
class 파일은 POJO 라고 부를 수 있으며, Servlet 파일은 Not POJO 라고 부를 수 있다.
2.
[ POJO 와 Not POJO 개념 ]
POJO :
Plain of Java Object
>>
아무것도 없는, 구식의 자바 객체 라는 의미 이다.
( == 오래된 방식의 순수한 자바 객체 를 의미 한다. )
본인이 평소에 공부용으로 만들었던, StudentVO, MemberVO, ..
일반 class 파일로 생성 하는 자바 클래스 들을 POJO 라고 한다.
Not POJO :
>>
말 그대로, POJO가 아니라는 뜻이다.
Servlet, Filter, Listener 등이 Not POJO 에 해당한다.
기본 제공 되는 기능들이 이미 많은 클래스를 Not POJO 클래스 라고 한다.
3.
서블릿 (Servlet) 생성 방법은 다음과 같다.

controller 패키지를 우클릭.

New - Servlet 클릭.

Class name을 작성해 주면 된다.

Class name은 FrontController 로 작성 하고, Finish 클릭.
(이미 동일한 이름의 서블릿 파일을 만들어 놓아서, 상단에 오류가 뜨는 모습.)
결론 부터 말하자면, FrontController 패턴 이라는 개념이 있다.
FrontController 패턴 이란 ?
1)
Action 인터페이스를 구현한 클래스 들은 execute() 메서드로 요청 처리를 한다.
이, execute() 메서드들은 모듈화가 되어 있다.
모듈화의 장점으로는 다음과 같다.
개발론적으로 봤을 때,
>> 클래스 파일이 나뉘어져 있기 때문에,
한개의 클래스의 코드에 문제가 발생 하더라도,
해당 클래스만 따로 코딩을 할 수 있기 때문에,
나머지 전체 코드는 사용이 가능 하다.
(한곳에 코드를 밀어 넣으면, 한개의 코드 파트가 고장 나면, 전체 부분 모두 STOP)
>> 필요한, VO와 DAO만 사용 할 수 있게 된다. (딱 필요한것만 사용 한다는 말)
기존에, .jsp 에서 Controller 을 구현 했을 때는,
useBean 액션태그와 setProperty 액션태그를 사용해서,
해당 페이지에서 사용되는 모든 VO, DAO, 멤버변수 (속성 / 프로퍼티) set 을 했었는데,
위와 같이 한 페이지에 작성 하지 않고, 모듈화를 하게 되면,
각각의 클래스 에서,
사용되는 VO와 DAO만 포함관계로 객체화 하는식으로 코드를 작성하기에,
모든 객체를 생성 하지 않아도 되게 된다.
2)
execute() 메서드의 output은 모두, ActionForward 객체 이다.
이러한 패턴으로 구성 되어 있기에, FrontController 패턴 이라고 한다.
3)
.do [ C ]
>> 상대적으로 가볍다.
.JSP [V]
>> 상대적으로 무겁다.
즉, 프론트 에게 데이터를 전송 하기 위해, (주고 받고) 필요한 Controller 이기 때문에,
FrontController 라고 한다. == FC
4.



그러면, 위와 같이, Not POJO 클래스가 생성 된다.
13번째 라인의 코드는 아래와 같다.
어노테이션 덕분에, 특정 Client (브라우저, 사용자) 요청에 대해 반응 할 수 있다.
또한, 실습을 위해, *.do 라고 작성 했다. 모든 .do 요청은 이쪽으로 온다는 의미 이다.
23번째 라인의 코드는 아래와 같다.
웹에서는 기본 생성자 (디폴트 생성자) 를 디폴트로 활용 하기 때문에,
반드시 필요한 기본 생성자 이다.
(기본 생성자를 사용 하기 때문에, 생성자 오버로딩을 하지 않는다.)
31번째 라인의 코드는 아래와 같다.
View 에서, <a href = " "> 식으로 데이터가 전송 되었다면, get 방식을 의미 한다.
40번째 라인의 코드는 아래와 같다.
View 에서, <form> 태그에 submit 으로 데이터가 전송 되었다면, post 방식을 의미 한다.
하지만,
get 방식으로 데이터가 오던지, post 방식으로 데이터가 오던지 무관 하게 하기 위해서,
아래와 같이 메서드를 새롭게 생성 하고, 31번째 라인의 코드와 40번째 라인의 코드를 수정 한다.



어떤 방식으로 데이터가 들어오더라도, 새롭게 만든 doAction() 이라는 함수를 호출 하도록 코드를 작성 했다.
5.
protected void doAction(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
doAction(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
위의 커스텀한 doAction() 메서드는, 웹 브라우저로 부터 데이터를 전송 받게 해주는 메서드 이다.
따라서, 해당 메서드 바디 { } 에서 코드를 (기능) 작성 하면 된다.
1)
Client == 사용자 == 브라우저 가 보낸 요청 (액션) 를 추출 해야 한다.
즉, 어떤 요청 (액션) 을 하고 싶었는지를 파악 해야 한다.
기존에는, 다음과 같이 코드를 작성 했다.
String action = request.getParameter();
하지만, 더이상 위의 코드를 작성 하지 않는다.
왜냐하면, 이제 더이상, action 값으로 보내는 방식을 사용 하지 않으려고 한다.
[ 개념 ]
예)
<% response.sendRedirect("main.do"); %>
.do 로 끝남 >> FrontController (FC) 로 가주세요.
<% response.sendRedirect("controller.jsp?action=main"); %>
.jsp 로 끝남 >> 해당 View 페이지를 보여 주세요.
(위의 경우는 Controller.jsp 페이지를 보여주는 상황.)
Controller.jsp?action=main
VS
main.do
action 이라는 파라미터 1개를 가지고 다녀야 해서, .jsp 로 코드를 작성 하는게 불리 하다.
파라미터 1개가 엄청 무거운 것은 아니지만, 그래도 .do 보다는 상대적으로 불리 하다.
(파라미터를 1개라도 아낄 수 있는 ~)
그러면 어떻게 해야 할까 ? 아래를 확인해 보자.
아래와 같은 방법으로

값이 어떻게 나오는지 확인 하기 위해 찍은 로그를 제외한 코드를 확인 하면,
3개의 코드가 작성 되어 있는 모습을 볼 수 있다.
request.getRequestURI() 메서드는 다음과 같다.

암튼, 프로젝트 + 파일경로를 String 으로 return 해주는 메서드 이다.
request.getContextPath() 메서드는 다음과 같다.

암튼, 프로젝트 Path를 String 으로 return 해주는 메서드 이다.
예시로, 로그를 확인 하면, 아래와 같다.
String uri = /day47_controller_practice/main.do
String cp = /day47_controller_practice
근데, 필요한것은 main.do 이기 때문에, substring() 메서드를 사용 했다.
cp.length() 만큼 uri를 잘라내서, String command 변수에 대입 한다.
그러면 String command 는 아래와 같게 된다.
String command = main.do
2)
추출한 요청에 맞는 Action 클래스의 execute() 메서드를 호출 한다.
execute() 메서드는, 1) 의 doAction() 메서드와 동일한 메서드 이다.
public void execute(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
메서드의 이름만 다를 뿐인데, 왜 execute() 라고 했냐면,
Spring 프레임워크 에서도 메서드명이 위와 같기 때문에, 이렇게 지어서 사용 했다.
(메서드를 호출 했기에, 기능을 수행하고, 요청을 처리 하게 된다.)
그런데, 현재 해당 서블릿 파일 내부에, doAction() 메서드의 { } 를 보면,
모 ~~~ 든 코드를 이곳에 작성하게 되는 구조일 수 밖에 없게 된다.
개발론적으로 봤을 때,
>> 클래스 파일이 나뉘어져 있기 때문에,
한개의 클래스의 코드에 문제가 발생 하더라도,
해당 클래스만 따로 코딩을 할 수 있기 때문에,
나머지 전체 코드는 사용이 가능 하다.
(한곳에 코드를 밀어 넣으면, 한개의 코드 파트가 고장 나면, 전체 부분 모두 STOP)
따라서, 모듈화가 필요 하다. == 유지 보수 용이.
그래서,
각각 모듈화가 필요한 코드에 대해, class 파일 (POJO) 을 생성 하는 방식으로 구현 한다.
SignupAction.java, SigninAction, SignOutAction, .. 으로,
class 파일을 만들어 주면 된다.
그리고, 해당 class 파일 내부에서, execute(request, response) 메서드를 작성 하면 된다.
그런데, 이 excute() 메서드는, 모든 POJO 클래스에서 사용 되고, 메서드 시그니쳐가 같다.
그래서
SignupAction.java, .. (POJO) 클래스 들을 정의할 클래스가 필요 하다는 생각을 할 수 있다.
>> 항상 사용 되며,
>> 정의 하지 않으면, 메서드 생성 과정에서, 실수가 발생 할 수 있는 가능성이 있고,
>> 반복을 줄이기 위해, (하드 코딩 XXX)
이 공통의 메서드의 설계 메서드인 추상 메서드 (미완성 메서드) 를 구현해 볼 수 있겠구나 ~
를 생각 할 수 있다.
이 추상 메서드를 가지고 있는 클래스는 abstract 키워드가 붙은 추상 클래스 이다.
그런데, 우리는 클래스의 멤버 모두가 필요한 상황이 아니라,
오로지, excute() 라는 추상 메서드만 필요한 상황이니까 ~
해당 메서드만 강제로 오버라이딩 하게 할 수는 없을까 ? 해서 사용 할 수 있는 방법이,
인터페이스 이다.
모든 Action류 POJO 클래스가 해당 인터페이스를 구현 해야 하기 때문에 ~
인터페이스 명을 Action 이라고 작성 했다.
인터페이스의 특징은 다음과 같다.
>>
인터페이스를 구현한 POJO 클래스는 인터페이스의 추상 메서드를 반드시 강제로 구현 해야 한다.
>>
메서드 시그니쳐를 지켜준다.
>>
접근제어자인 public 키워드가 디폴트 이고, abstract 키워드도 디폴트 이다.
>>
따라서, 위의 두 키워드는 당연한 개념이기에, 생략해서 작성이 가능 하다.


}
09번째 라인의 public 키워드도 생략이 가능 하다.
간단하게, interface Action { } 라고 작성 해주면 된다.
모든 Action 류 POJO 클래스들이, execute() 추상 메서드를 구현 해야 하기 때문에,
위와 같이 인터페이스로 내부에 작성됬으며,
각각의 execute() 메서드들은, output이 ActionForward 타입 이어야 한다.
그래서, return 타입이 현재 위의 이미지와 같은 상황인데, 그 이유는 다음과 같다.
JAVA는 output을 2개 이상 전달 할 수가 없다.
무조건 JAVA 에서 함수의 output은 1개 이다.
그런데, 만약에 2개 이상의 데이터를 전송 해야 하려면 어떻게 했는가 ?
바로 객체에 필요한 데이터를 set 해서 보내주면 된다 !
그래서 ActionForward 라는 이름의 VO 클래스를 생성 해서,
return 타입으로 작성한 모습을 확인 할 수 있다.
왜 2개 이상의 데이터를 전송 해야 하는지는 다음과 같다.
DB 에서 받은 데이터를 Controller.java 로 돌아갈 준비를 한다.
그런데,
데이터를 C에게 줘야만 하는건지, 주지 않아도 되는건지를 파악 해야 하고,
어디 페이지로 이동 해야 하는지도 파악 해야 한다.

그래서 위의 이미지와 같이, ActionForward 라는 VO 클래스를 생성했다.
FrontController 서블릿을 확인해 보자.


command가 /signup.do 랑 같아 ?
그러면, new SignupAction() 를 주체로 해서 execute() 메서드를 사용하는 모습을 확인 할 수 있다.
해당 메서드를 실행 하고, 데이터를 forward 참조변수에 대입 하는 모습 이다.
[ 참고 ]
SignupAction signupAction = new SignupAction();
signupAction.execute(request, response);
위와 같이,
참조변수를 선언해서, 객체를 생성하고, 참조변수를 주체로 해서 execute() 메서드를 사용 해도 된다.
하지만, 객체를 가지고 꼭 무엇을 해야 하는 상황이 아니기에,
위의 이미지는, 참조변수를 선언 하지 않고, 무명 객체를 사용해서 표현한 상황 이다.
(바로 new 해서)
SignupAction 클래스를 확인해 보자.

해당 클래스는 Action 이라는 인터페이스를 구현한 클래스 이기 때문에,
implements 키워드를 사용한 모습 이다.
인터페이스의 추상 메서드를 상속 받아서, { } 을 붙혀서, 기능을 구현한 모습이다.
회원가입의 기능을 하는 해당 페이지는,
V 으로 부터, 아이디, 비밀번호, 이름의 데이터를 get 해서,
mVO 객체에 set을 해주고, DB에 insert() 메서드를 통해 데이터를 넣으면 끝이 난다.
그래서,
flag가 false 이면,
무언가 에러로 DB에 insert() 가 안된 상황이니까, forward 객체를 null 로 해서 반환 하고,
flag가 true 이면,
새롭게 forward 객체를 생성 해서, forward가 두개의 멤버변수를 set 하게 된다.
이때, setRedirect(true) 인것은, 데이터 전송이 필요 없다는 것을 의미 한다.
그리고, setPath("main.do") 를 통해, 경로를 설정 해 준다.
해서, 2가지 데이터가 담긴 객체를 return 한다.
3)
요청 처리가 완료 되면, 사용자에게 응답을 해야 한다.
View 으로 다시 이동 해야 한다는 의미 이다.
6.
FrontController.java 이다.

이렇게 데이터를 forward 참조변수에 저장하고,
이 데이터를 다시, View 로 보내 줘야 한다.

forward 가 null이 아니어야, null pointer exception이 안뜨는 상황이므로,
로직을 위와 같이 구현 해 놓고,
forward.isRedirect() 가 true 이면, 데이터 전송할게 없다는 의미 이다.
그래서, 이런 경우는, forward 가 set한 경로를 다시 forward가 get 해서,
그 경로값을 response.sendRedirect() 의 인자로 넣어 준다.
그러면 V 에게 데이터가 전송 된다.
forward.isRedirect()가 false 이면, 데이터를 전송해야 한다는 의미 이다.
그래서, 이런 경우는, RequestDispatcher를 사용 해서,
경로와, 데이터를 V 에게 보내 준다.
RequestDispatcher dispatcher = request.getRequestDispatcher(forward.getPath());
dispatcher.forward(request, response);
forward가 null 이면 ???
PrintWriter out = response.getWriter();
out.println("<script>alert(' 요청 처리 실패..');history.go(-1);</script>");
out.close();
위와 같이 코드를 작성 했는데,
(<script> 태그는 사실 View 파트라서, 여기서 작성 하면 안된다.)

해석
PrintWriter 클래스는 간단한 설명하자면, 바이트를 문자 형태를 가지는 객체로 바꿔준다.
클라이언트에게 문자 형태로 응답을 하고 싶기 때문에,
out이라는 PrintWriter 클래스 객체를 정의하고 getWriter() 메소드를 통해 인스턴스를 얻었다.
getWriter() 메소드를 통해 응답으로 내보낼 출력 스트림을 얻어낸 후,
out.print(HTML 태그) 형태로 작성하여 스트림에 텍스트를 기록한다.
위 소스에는,
서버가 문자열을 보내는 응답정보 안에 어떤 타입의 문서를 보낸다고 설정해주는 부분이 없다.
그러면 웹 브라우저는 기본값으로 처리한다.
기본값은 text/html 이다.
따라서 웹 브라우저는 응답받은 문자열을 모두 HTML 태그로 인식하여 처리한다.
코드의 마지막에 보면 out.close() 를 통해 스트림을 닫았다 (자원 해제).
여기서 유의할 점은, close() 메소드를 통해 한 번 닫힌 스트림을 다시 사용할 수 없다.
스트림을 비우고 필요시 계속 사용할 수 있는 flush() 메소드와의 차이점이 여기에 있다.
close()를 호출하는 이유는,
작업을 종료하고 스트림을 닫아 종료된 작업에 대한 메모리를 확보하기 위함이다.
요약
Servlet에서 클라이언트의 요청(Request)에 대한 응답(Response) 형태는,
문자(character) 또는 바이트(byte)가 될 수 있다.
클라이언트의 요청에 문자 형태로 응답하려면 데이터의 흐름(Stream)을 컨트롤 해야 한다.
즉, 텍스트 (== 문자) 형태로 응답을 보내도록 설정해야 한다.
HttpServletResponse 인터페이스의
상위 인터페이스인 ServletResponse의 getWriter() 메소드를 호출하면
스트림에 텍스트를 기록하는 것이 가능하다.
'Front-end (국비) > 이론' 카테고리의 다른 글
비동기 처리 (0) | 2023.07.20 |
---|---|
1 : N 구조 개념 & 서블릿 (Servlet) 개념 복습 (1) | 2023.07.18 |
리스너 클래스 (0) | 2023.07.06 |
필터 클래스 (0) | 2023.07.05 |
커스텀 태그 (0) | 2023.07.05 |