일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 왜불변객체인가
- rest api 검증
- 스프링요청반응
- 공유기작동방식
- 동등성동일성
- fcmv1
- 검증 실패 예외처리
- fcm데이터구조
- 래퍼클래스
- HttpServlet
- biblecash
- 옵티마이저
- Wrapper class
- 불변객체
- equals
- HTTP프로토콜
- httpservlet기술
- 데이터베이스파서
- fcm성능비교
- 클라이언트요청반응
- 동기비동기블로킹논블로킹
- rest api
- java enum
- multipart바인딩
- multiparfile데이터
- DispatcherServlet
- 디스패처서블릿
- 프로세스 생성
- 옵티마저
- 중첩클래스
- Today
- Total
개발은 아름다워
[ Spring ] 고대의 서블릿을 찾아서(4) 수문장인 FrontController의 등장 본문
이전까지의 흐름을 살펴본다면
서블릿 -> 템플릿 엔진 -> mvc패턴 이런 흐름으로 점차 발전하였다.
이전의 불편함을 개선하여 조금씩 발전한 형태로 만들어졌다.
이번에는 mvc패턴에서 단점으로 지적되었던 점을 개선한 FrontController를 만들어 볼 것이다.
왜 프론트 컨트롤러가 필요한가?
MVC 패턴에서는 각각 요청 URI에 맞는 서블릿이 존재했다. 이 때 각각의 서블릿은 요청을 처리하는데 있어서 중복되는 코드들이 있었다. 따라서 이러한 중복되는 부분들을 공통으로 묶고 처리할 수 있는 방법이 있지 않을까?에서 출발한게 프론트 컨트롤러이다. 프론트컨트롤러 패턴은 다음과 같은 특징이 있다.
frontController 패턴 특징
- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받는다.
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출한다.
- 공통 처리가 가능하다.
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.
- 스프링 웹 MVC의 핵심도 바로 FrontController이며 스프링 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있다.
코드로 봐보자
일단 전체적인 맥락은 이렇다.
1. 클라이언트로부터 요청이 들어온다.
2. 요청이 frontController는 서블릿이므로 프론트컨트롤러의 urlpattern에 해당하는 요청이 들어오면 프론트컨트롤러 객체는 service를 실행한다.
3. service안에 코드들이 실행되면서 controllerMap에서 요청 URI에 해당하는 controller 찾는다.
4. controller.proccess() 를 실행시킨다.
5. controller.proccess가 실행되면 각 요청에 맞는 jsp 서블릿에게 데이터를 전달한다.
6. jsp 서블릿은 브라우저에 응답 데이터를 내려준다.
이전에는 요청 하나 하나에 대한 서블릿을 만들었다면, 서블릿은 하나지만 요청 URI를 해결할 controller 만들 것이다. 여러개의 controller가 만들어질 것이다. 그러나 공통적인 수행할 메서드는 같다. 따라서 다형성을 활용해 Controller 인터페이스를 만든 후 Controller 인터페이스를 상속 받고 URI를 해결할 수 있는 컨트롤러 구현체를 만들것이다.
Controller 만들기
== controller 인터페이스 ==
public interface ControllerV1 {
void process(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException;
}
1. Form 데이터를 만들 컨트롤러
public class MemberFormControllerV1 implements ControllerV1{
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
2. save를 할 수 있는 컨트롤러
public class MemberSaveControllerV1 implements ControllerV1{
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
// Model에 데이터를 보관
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
3. MemberList를 불러오는 컨트롤러
public class MemberListControllerV1 implements ControllerV1{
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
FrontController 전체 코드
@WebServlet(name = "frontControllerServletV1",urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet{
private Map<String,ControllerV1> controllerMap = new HashMap<>();
public FrontControllerServletV1() {
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save",new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if(controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
controller.process(request, response);
}
}
수문장 역할인 frontController의 코드는 위와 같다. 코드를 분석해보자.
일단 요청은 여기로 다 와!
@WebServlet(name = "frontControllerServletV1",urlPatterns = "/front-controller/v1/*")
urlPatterns을 보면 "/front-controller/v1/*" 되어있다. 즉 /front-controller/v1/ 뒤에 어떤 패턴이 오더라도 일단은 프론트컨트롤러가 받을 수 있다는 것이다.
Map을 이용하여 요청 URI와 controller 매칭 시키기
controller는 만들어져있다. 이제 만들어진 controller와 요청 uri만 매칭 시키면 된다.
private Map<String,ControllerV1> controllerMap = new HashMap<>() 만들어서 key는 요청 uri, value는 contoller로 매칭 시키면 된다. 그렇다면 언제 매칭시키면 될까? 바로 frontController 객체가 생성될 때 매칭시키면 된다. 따라서 생성자를 이용하게 되며 코드는 다음과 같다.
private Map<String,ControllerV1> controllerMap = new HashMap<>();
public FrontControllerServletV1() {
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save",new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
요청은 어떻게 실행되는거지? 그건 바로 servlet의 service!
URI 요청이 들어오면 프론트컨틀러는 @WebServlet(name = "frontControllerServletV1",urlPatterns = "/front-controller/v1/*")
이 적용되어 있기 때문에 service가 실행된다.
그렇다면 service안의 코드를 봐보자
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if(controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
controller.process(request, response);
}
- request.getRequestURI()로 요청 URI를 만든 후
- controllerMap.get(requestURI) controllMap에서 요청 URI에 맞는 controller를 가져온다.
- 요청 URI에 해당하는 controller가 없을 경우 null이 되어 브라우져 setStatus로 HttpServletResponse.SC_NOT_FOUND 응답값을 내려준다.
- 요청 URI에 해당하는 controller가 있다면 controller.process(request, response)가 실행된다. 이 부분을 보면 왜 Controller 인터페이스를 만든지 알 수 있다.
정리
mvc패턴에서 보였던 문제들이 해소 되었다. 그러나 아직도 아쉬운 부분이 있는데 모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있다는 것이다. 다음 단계에서는 이 부분도 깔끔하게 분리하기 위해 별도의 뷰를 처리하는 객체를 만들것이다.
'자바' 카테고리의 다른 글
[ Java ] socket을 이용한 실시간 채팅 프로그램을 만들어보자 (ver1) (0) | 2024.10.22 |
---|---|
[ Java-OOP(1) ] 객체를 쓴다고 객체지향 프로그래밍이 아니다! - 절차지향과 객치향의 차이 (0) | 2024.10.18 |
[ Java ] 제네릭은 무엇이며 왜 쓰는걸까? (1) | 2024.10.12 |
[ Java ] equals는 왜 쓰는거고 어떻게 쓰는걸까? - 동일성과 동등성 (0) | 2024.10.12 |
[ Java ] 중첩 클래스, 내부 클래스란? (0) | 2024.10.12 |