지금까지는 controllerMap에 특정 인터페이스를 지정하고 매핑하였기 때문에, 다른 인터페이스를 준수하는 컨트롤러는 매핑할 수 없었다.
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4() {
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
...
FrontController가 다양한 방식의 컨트롤러를 처리할 수 있게 하기 위해서는 어댑터 패턴을 적용해야 한다.
중간에 어댑터 역할을 하는 어댑터가 추가되었는데 이를 핸들러 어댑터라고 한다. 덕분에 다양한 종류의 컨트롤러를 호출할 수 있게 되었으며, 이에 컨트롤러의 이름을 더 넓은 범위인 핸들러라고 변경한다.
핸들러 어댑터 구성
핸들러 어댑터는 크게 Bool 값인 supports와 handle로 나뉜다.
# supports
해당 컨트롤러를 처리할 수 있는지 판단하는 메서드로 Bool 값을 반환한다.
# handle
실제 컨트롤러를 호출하고, 그 결과로 ModelView를 반환한다. 만약 실제 컨트롤러가 ModelView를 반환하지 못하면, 어댑터가 ModelView를 직접 생성해서라도 반환해야 한다.
이전에는 FrontController가 실제 컨트롤러를 호출했지만, 이제는 이 어댑터를 통해서 실제 컨트롤러를 호출한다.
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse reponse) throws ServletException, IOException;
}
Adapter 구현 - ControllerV3 핸들러
# supports
ControllerV3를 처리할 수 있는지 instanceof로 질의한다.
# handle
ControllerV3로 타입 캐스팅한 controller를 선언한 다음, request 정보를 모두 담은 paramMap를 매핑시킨다. ControllerV3는 process 메서드를 통해 ModelView를 반환하기 때문에 그대로 return 하면 되지만, 다른 유형의 Controller의 경우 적절한 로직 작성이 요구된다.
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(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;
}
}
FrontControllerServlet
특정 컨트롤러 인터페이스를 지정해서 인자로 넣었던 이전 controllerMap과 다르게, 모든 컨트롤러를 지원하기 위해 Object를 인자로 넣은 handlerMappingMap을 생성한다.
그리고 각 어댑터를 답을 handlerAdapters라는 어댑터 리스트도 생성한 다음, 매핑 맵과 어댑터 리스트를 초기화한다.
@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<>();
// initialize
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
...
}
이제 service 메서드를 작성한다.
먼저 핸들러를 가져와야 한다. HttpRequestServlet에서 URI를 가져온 다음, handlerMappingMap에 질의하여 부합하는 handler가 있는지 조회한다.
...
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request);
if (handler == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
...
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
...
이 handler가 V3인지 V4인지 확인하였기 때문에, 이를 처리할 수 있는 어댑터 목록에서 어댑터를 찾아와야 한다. 앞서 초기화한 handlerAdapters에서 loop를 통해 찾는다.
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
...
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
MyHandlerAdapter foundAdapter = getHandlerAdapter(handler);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter: handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
// 예외 반환
throw new IllegalArgumentException("Adapter를 찾을 수 없습니다. handler" + handler);
}
...
}
이제 handler에 맞는 adapter까지 찾았기 때문에 adapter의 handle 메서드를 통해 ModelView를 반환하고, ViewResolver를 통해 MyView를 반환한다. 이를 렌더링 하여 뷰를 호출한다.
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
...
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
ModelView mv = adapter.handle(request, response, handler);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
...
}
다른 Controller 인터페이스(ControllerV4)를 Adapter에 매핑
이번에는 ControllerV4 인터페이스를 어댑터에 추가해보도록 한다. ControllerV4는 매번 ModelView 객체를 생성하여 반환했던 ControllerV3를 ViewName만 반환하도록 개선한 것이다.
어댑테만 추가하면 되기 때문에 FrontControllerServlet에는 initialize 코드만 추가하면 된다.
{
...
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
...
}
ControllerV4의 어댑터이다. process 메서드를 통해 바로 ModelView를 반환하였던 ControllerV3와 달리 ControllerV4는 viewName을 반환하기 떄문에, 이 viewName을 가지고 ModelView를 만들어서 반환하도록 코드를 작성한다.
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV4 controller = (ControllerV4) handler;
Map<String, String> paramMap = createParamMap(request);
HashMap<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView mv = new ModelView(viewName);
mv.setModel(model);
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;
}
}
'🌱 Spring > MVC ①' 카테고리의 다른 글
[Spring] MVC _ @RequestMapping (2) (0) | 2023.07.22 |
---|---|
[Spring] MVC _ @RequestMapping (1) (0) | 2023.07.22 |
[Spring] MVC _ 개발자를 위한 기능 개선 (0) | 2023.07.17 |
[Spring] MVC _ ViewModel 추가 (0) | 2023.07.17 |
[Spring] MVC _ Front Controller 도입, View 분리 (0) | 2023.07.17 |