spring하면 mvc 패턴의 framework로 잘 알고 있다. 하지만 이번에는 spring은 사용하지 않고 지금까지 공부한 servlet을 활용하여 mvc 패턴을 구현해보려고 한다.
위 그림과 같이 기존의 MVC 패턴으로 구현한 Servlet은 client의 하나의 url 호출이 있을때 하나의 매칭되는 하나의 servlet에서 controller와 같이 정보를 넘겨주는 방식이였다. 하지만 작동하는 servlet을 url마다 생성해야하고 하는 동작은 url의 값을 읽어와 view와 연결시켜주는 코드는 사용자가 요청한 서비스 로직을 제외하면 계속해서 반복되어지는 코드가 발생했다. 그래서 이 상황을 더 개발자스럽게 해결하기 위해 FrontController 패턴이 등장했다.
위 그림처럼 하나의 FrontController를 생성하여 모든 client의 요청을 하나의 서블릿에서 받아 각각 매칭되는 controller로 넘겨주는 역할을 하는 FrontController를 생성하였다. 이제 이 방식을 코드로 직접 작성해보자.
Controller interface 생성
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
controller 인터페이스를 생성하여 각각의 controller들은 로직의 일관성을 부여할 수 있다. 모든 controller는 ControllerV1을 상송박아 process에서 로직을 처리하는 것이다.
회원 등록 controller 생성
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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);
}
}
로직은 기존의 servlet에서 처리하던 로직과 거의 동일하다. 그럼에도 우리가 이렇게 분리하는 이유는 하나의 servlet을 통해 각각의 controller들을 분리하기 위해서이다.
FrontController 생성
import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@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");
//도메인 이후의 url 값을 가져옴
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
//페이지에 대한 정보가 없을 경우 404 상태값 리턴
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
controller.process(request, response);
}
}
위 예제 코드는 한개이지만 이렇게 생성자에 여러 url들과 각각의 매칭 controller 객체들을 생성하여 map에 담아둔다. 그리고 servlet의 service에서는 client에서 요청한 url의 정보를 확인하여 매칭되는 controller의 정보를 가져와 해당 controller의 process를 실행시키는 과정이다.
V1 모델
V1 모델의 client 요청 과정을 살펴보면
V2가 나온 이유는 모든 controller에서 view로 이동하는 부분이 계속해서 반복되는 코드가 되어버렸다.
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
다음 코드를 공통으로 처리하고 싶어진 것이다.
그럼 V2 패턴으로 다시 리팩토링해보자!
MyView Class 생성
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
MyView라는 class를 공통으로 사용하기위해 먼저 생성해둔다. MyView에서는 client로 전달될 view의 자원의 주소값과 dispatcher의 forward를 실행시켜주는 render()라는 메서드를 생성해둔다.
회원 등록 Controller 생성
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MemberSaveControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView 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);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
그럼 기존의 v1과 차이점이 보일것이다.
v1
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
v2
return new MyView("/WEB-INF/views/save-result.jsp");
기존의 v1에서 다음과 같이 긴 코드를 MyView란 객체 생성을 통해 간결하게 변경된 부분을 확인할 수 있다.
FrontController 생성
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1;
import hello.servlet.web.frontcontroller.v2.controller.MemberFormControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberListControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberSaveControllerV2;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "FrontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerMap = new HashMap<>();
public FrontControllerServletV2() {
controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
}
//하나의 서블릿을 통해 여러 컨트롤러에 값을 연결해주는 역할
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//도메인 이후의 url 값을 가져옴
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
//페이지에 대한 정보가 없을 경우 404 상태값 리턴
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
MyView view = controller.process(request, response);
view.render(request, response);
}
}
FrontController의 경우도 코드가 변경되었는데
MyView view = controller.process(request, response);
view.render(request, response);
마지막에 해당 코드가 변경되어졌다. 그 이유는 기존 process에서 처리되던 화면 렌더링 과정이 controller의 process에서 MyView객체를 반환받아서 해당 객체를 통해 render 메서드를 실행시켜야하기 때문이다. 이 방법을 통해 우리는 process에서 계속 반복해서 화면 렌더링 과정을 코드로 적어줬어야했는데 render라는 메서드 하나를 호출하는 것으로 줄여버릴 수 있게되었다.
V2 모델
V2 모델의 client 요청 과정을 살펴보자
v1과 v2의 차이점으로는 view로 전달하는 과정을 메서드화 시킴으로 인해 반복해서 구현해야했던 자원의 주소값과 해당 주소의 dispatcher.forward() 코드를 render()로 간편하게 구현할 수 있도록 변경되었다.
Servlet에서 모든 로직을 처리하던 과거 버전에서 이제는 servlet의 로직을 controller와 분리하여 서비스 로직은 controller의 process를 통해서 처리하게 되었다. 그런데 이렇게 변경이 되어지니 controller에서 굳이 HttpServletReqeust와 HttpServletResponse의 정보를 계속 가지고 있어야하는 것일까? 굳이 그럴 필요가 없다. 그래서 Servlet과 Controller를 완전히 분리하고자 V3패턴이 등장했다.
서블릿의 종속성을 제거함으로 인해 구현 코드도 매우 단순해지고 테스트 코드 작성이 매우 쉬워지게 된다.
ModelView 생성
지금까진 model의 정보를 담기 위해 request.setAttribute()를 통해서 정보를 담아 view에 전달했었다. 하지만 서블릿의 종속성을 제거하기 위해 Model을 직접 만들고 View 이름까지 전달하는 객체를 ModelView라는 객체를 통해서 하려고한다.
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
@Getter
@Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
}
```
view의 자원의 주소를 찾기위한 viewName과 model에 담을 정보를 담기 위한 map을 선언해준다.
ControllerV3 인터페이스 생성
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
위에서는 ControllerV2는 생성 코드는 보여주지 않았다. 그 이유는 V1과 다르지 않기 때문이였다. 하지만 V3는 V1,V2와는 다르게 process에 paramMap이란 Map을 받아야한다. 이 paramMap은 기존 httpServlet을 받지 않기 위해 map으로 변경된 구조이다.
v1, v2
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
v3
ModelView process(Map<String, String> paramMap) throws ServletException, IOException;
회원 등록 Controller 생성
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import java.util.Map;
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
Integer age = Integer.valueOf(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
v2와 차이점부터 확인해보자!
v2
request.setAttribute("member", member);
return new MyView("/WEB-INF/views/save-result.jsp");
v3
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
v2와 v3를 비교하면 v3가 더 길어진거 같다. 하지만 기존의 request에 model의 정보들을 담는 방식에서 ModelView객체를 통해 model이라는 map을 불러오고 해당 map에 우리가 view로 전달하려는 정보를 담아준다. 이것의 차이점은 이제 controller에서는 HttpServlet의 기능을 사용하지 않아도 된다는 것이고 그 점으로 인해 HttpServlet의 종속적이지 않게 되었다.
v2와 v3를 비교하면 v3가 더 길어진거 같다. 하지만 기존의 request에 model의 정보들을 담는 방식에서 ModelView객체를 통해 model이라는 map을 불러오고 해당 map에 우리가 view로 전달하려는 정보를 담아준다. 이것의 차이점은 이제 controller에서는 HttpServlet의 기능을 사용하지 않아도 된다는 것이고 그 점으로 인해 HttpServlet의 종속적이지 않게 되었다.
FrontController(=Servlet) 생성
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberFormControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberListControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberSaveControllerV2;
import hello.servlet.web.frontcontroller.v3.controller.MemberFromControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "FrontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFromControllerV3());
controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
//하나의 서블릿을 통해 여러 컨트롤러에 값을 연결해주는 역할
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//도메인 이후의 url 값을 가져옴
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
//페이지에 대한 정보가 없을 경우 404 상태값 리턴
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
//paramMap
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
기존의 프론트 컨트롤러들에 비해 코드가 많이 복잡해진 기분이다... 하지만 어쩔 수 없다. 우리가 고생할 수록 사용자들이 편해진다고 한다. 우리가 고생해야한다. 우선 기존의 방식과 같이 url을 통해 controller들을 구별해내고 controller의 process를 실행시킬건데 우리는 더이상 process에 servlet을 매개변수로 사용하지 않고 우리가 만든 paramMap을 사용할것이다.
createParamMap 메서드
request의 정보를 통해 paramMap에 request에 담긴 정보들을 그대로 가져와 다시 담고 반환해주는 메서드이다.
createParamMap을 통해 우리가 사용할 map을 만들고 해당 controller의 process를 통해 ModelView 객체를 반환 받고 ModelView에서 viewName 정보를 가져와 viewResolver 메서드를 실행시킨다.
viewResolver 메서드
기존의 중복으로 계속 적어줘야했던 "/WEB-INF/views/"와 ".jsp" 주소값을 중간의 중복되지 않던 viewName만 받아와서 MyView를 반환시켜주는 메서드이다.
viewResolver를 통해 반환받은 MyView의 render를 통해 view로 정보를 전달해주는 과정이다.
V3 모델
V3 모델의 요청과정은
V3 모델은 지금까지 사용되던 Servlet을 controller와 완전히 분리하기 위한 작업들과 viewResolver 메서드를 통해 중복으로 코딩해야했던 자원의 주소값을 간단하게 입력할 수 있도록 변경하였다.
앞서 만든 V3 컨트롤러는 서블릿의 종속성을 제고하고 뷰 경로의 중복을 제거하는 등 잘 설계된 컨트롤러이다. 하지만 항상 ModelView 객체를 생성하고 반환해야하는 부분이 조금 번거롭다. 좋은 아키텍쳐도 중요하지만 개발자가 쉽게 사용하는 실용성도 중요하기 때문에 이번 V4 패턴은 V3를 좀더 실용적으로 리팩토링하기 위한 버전이다.
ControllerV4 생성
import java.util.Map;
public interface ControllerV4 {
/**
* @param paramMap
* @param model
* @return viewName
*/
String process(Map<String, String> paramMap, Map<String, Object> model);
}
V4는 ControllerV3와는 다르게 ModelView를 직접 반환하지 않고 String만 반환하게 설계한다. 또한 ModelView의 model을 직접 호출하여 정보를 담는 방식이 아니라 model 자체를 함께 받는 식으로 변경하였다. 반환하는 정보는 뷰의 이름으로 반환하려고 한다.
회원 등록 Controller 생성
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import java.util.Map;
public class MemberSaveControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
String username = paramMap.get("username");
Integer age = Integer.valueOf(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
model.put("member", member);
return "save-result";
}
}
이제 컨트롤러에서는 전달 받은 model객체에 우리가 view로 전달하고자 하는 정보를 담고 view의 이름만 반환해주면 된다. 개발자 입장에서는 엄청 간편해졌다.
V4 모델
V4 모델의 요청과정은V4 모델은 V3를 조금 리팩토링한 모델이므로 많은 수정은 없었다. 거의 동일한 동작을 수행하지만 개발자 입장에서는 엄청 간편하게 작동할 수 있도록 코드가 리팩토링되었다.
V4 패턴까지의 프론트 컨트롤러는 한가지의 컨트롤러 인터페이스만 사용할 수 있다. V3와 V4는 완전히 다른 방식으로 구현되어 있는 인터페이스라 호환이 불가능하다. 그래서 나온 방식이 어댑터 패턴이다. 어댑터 패턴을 사용하여 프론트 컨트롤러가 다양한 컨트롤러를 처리할 수 있도록 변경해보자.
MyHandlerAdapter 인터페이스 생성
import hello.servlet.web.frontcontroller.ModelView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface MyHandlerAdapter {
boolean support(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
MyHandlerAdapter에서는
support 메서드
handle 메서드
adapter 구현
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean support(Object handler) {
return (handler instanceof ControllerV3);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV3 controller = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
return mv;
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
V3를 사용하기 위한 adapter를 생성했다. 인터페이스에서 구현해놓은 메서드들을 실제로 구현해봤는데
FrontController 생성
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.controller.MemberFromControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.servlet.web.frontcontroller.v4.controller.MemberFromControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV4HandlerAdapter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
initHandlerMappingMap();
intiHandlerAdapters();
}
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFromControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
}
private void intiHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//도메인 이후의 url 값을 가져옴
Object handler = getHandler(request);
//페이지에 대한 정보가 없을 경우 404 상태값 리턴
if(handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
// adapter 가져오기
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView mv = adapter.handle(request, response, handler);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.support(handler)){
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = " + handler);
}
private MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
}
initHandlerMappingMap()
서블릿에서 매핑되는 url의 정보와 url과 연결되는 컨트롤러의 정보를 담아준다.
intiHandlerAdapters()
서블릿에서 사용할 어댑터의 정보들을 담아준다.
위의 메서드들을 통해 각 컨트롤러와 어댑터의 정보를 담아준다. 그리고 service에서는
이렇게 처리되는 과정을 다른 Controller를 추가하기 위해서 우리는 이제 dapter와 서블릿에 해당 adapter에 대한 정보를 추가해주는 것으로 다른 컨트롤러들을 모두 사용할 수 있게 되었다.
V5 모델
V5 모델의 요청과정은
이렇게 MVC 패턴의 프레임워크를 직접 만들고 리팩토링을 통해 더 나은 방향으로 개선해봤다. 이 동작 구조는 다음에 공부할 Spring MVC 패턴과 같으므로 꼭 기억하고 다음 공부에서는 Spring은 어떻게 이걸 우리가 쉽게 사용할 수 있도록 해주었는지 확인해보자!