지금까지 우리는 V1~V4까지 MVC 패턴을 직접 개선해가면서 코드를 작성해보았다
정리하자면 다음과 같은 점진적 개선과정을 거쳤다
v1: 프론트 컨트롤러 도입을 통해 공통처리를 가능하게 했다
이로써 프론트 컨트롤러 이외에 다른 컨트롤러들은 서블릿 컨트롤러로 생성할 필요가 없다(@WebServlet 사용 필요 X)
v2: view 분류
단순 반복되는 view forwarding 프로세스를 MyView 객체의 생성을 이용하여 개선했다.
MyView의 render 메서드를 통해 RequestDispatcher를 생성하고 forward를 함으로써 JSP에 request, response를 전달했다.
이 객체의 생성은 viewPath 절대경로를 받음으로써 일어난다(생성자의 인자로 절대경로를 받는다)
v3: model 추가
컨트롤러에서 서블릿 종속성을 제거했다. HttpServletRequest, HttpServletResponse
그 대신 model 전달과 viewName을 가져오기 위한 ModelView를 반환값으로 추가했다.
ModelView 는 viewName 필드와 <Key, Value>가 각각 String, Object인 model 필드를 가졌다.
뷰 절대경로를 인자로 사용함에 따라 발생하는 중복 코드를 논리뷰 이름을 반환함으로써 제거했다
그 대신 viewResolver 메서드를 프론트 컨트롤러에 추가하여 논리뷰 이름을 물리뷰로 변환할 수 있도록 했다
-> 변경 포인트를 하나로 줄이는 것이 좋은 설계임을 기억하자
v4: 편의성을 위해 ModelView 객체를 반환하는 process를 단순 논리뷰를 반환하게 끔 코드를 변경하고
Model은 프론트컨트롤러가 만들게 하고 컨트롤러는 그 인자를 받아서 사용하게 끔 코드 변경
이로써 논리뷰를 간단히 String으로 반환할 수 있게 변경함 -> 개발 편의성 측면 리팩터링
이렇게까지 쭉 점진적으로 프레임워크를 개선해왔는데 이제 한가지 의문이 남는다
-> 이것들 전부 혼용해서 사용할 수는 없을까?
사람들 각자마다 적용하고자 하는 개발 방식이 다를 수 있는데, 이걸 어떻게 모두 적용할 수 있을까?
정답은 어댑터 패턴이다
지금까지 우리가 개발한 프론트 컨트롤러는 한가지 방식의 컨트롤러 인터페이스만 사용할 수 있는데
서로 다른 컨트롤러를 한번에 적용하기는 어렵다, 이럴 때 사용하는것이 바로 어댑터 패턴이다.
어댑터 패턴을 이용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 설계를 변경해보겠다
이전에 V4와는 다르게 핸들러라는 이름의 요소들이 많이 추가된 것을 볼 수 있다.
여기서 핸들러 어댑터는 중간에 어댑터 역할을 하는데 이 친구 덕분에 다양한 컨트롤러를 호출할 수 있다.
핸들러 : 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다.
어댑터가 있기 때문에 이제 컨트롤러의 개념뿐아니라 어떤 것이든 해당하는 종류에 어댑터만 있으면 다 처리할 수 있기 때문
차근차근히 짚어보면 다음과 같은 과정을 거쳐서 처리가 일어난다.
FrontController에 요청이 들어오면 URI와 컨트롤러(여기서는 어댑터)의 조합인 handlerMappingMap(과거의 controllerMap)에서 컨트롤러를 get해서 handler로 반환한다. (여기서 반환 타입은 Object 타입인데, 그 이유는 handler를 통해 adapter를 찾는 과정에서 이 handler가 이 adapter에 맞는 handler인지를 판가름하는 supports(Object handler) 메서드가 실행되어야하기 때문이다.
즉, 아직 반환된 핸들러가 어떤 어댑터에 맞는지 알 수 없기 때문에 Object로 반환한다.
getHandlerAdapter(handler)를 통해 adapter를 찾아냈다면, adapter의 handle 메서드를 호출
request, response, handler를 인자로 전달한다
handle에서는 supports() 메서드를 통해 타입이 검증되었기 때문에 먼저 해당 타입으로 컨트롤러를 Casting하는 작업이 일어난다.
파라미터가 저장된(이전에 v3에서 서블릿 종속성을 제거함) paramMap을 process의 인자로 전달한다. (v3)
그리고 ModelView를 반환타입으로 갖는다
반면 v4의 handle 메소드에서는 process에 paramMap과 model을 모두 전달하고 반환값으로 문자열 논리뷰 이름을 받는다
핸들러 어댑터는 이렇게 각 Controller의 process를 호출하는데 모든 결과가 통일되도록, 모든 컨트롤러를 사용할 수 있도록 전처리를 거치게 해준다.
코드를 보자 어댑터는 결론적으로 모든 컨트롤러가 V3처럼 일할 수 있도록 처리한다
-> 이는 FrontController의 구현을 변경하면 V4처럼도 일하게 할 수 있다.
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
다음 어댑터는 ModelView를 반환한다, handle의 handler를 인자로 보낸다 (어떤 타입의 컨트롤러인지 판단)
어댑터 V3
package hello.servlet.web.frontcontroller.v5.adapter;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV3);
// Object가 ControllerV3의 인스턴스인가?
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws ServletException, IOException {
// Object로 받는 이유는 유연성을 위함
ControllerV3 controller = (ControllerV3) handler;
// Casting 해도 괜찮음, frontController에서 supports로 검증함.
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
return mv; // 반환 타입을 맞춰서 반환해줘야 함.
// 근데 V4는 논리뷰 이름만 반환해서 거기서는 로직이 달라짐.
}
private static 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;
}
}
어댑터 V4
package hello.servlet.web.frontcontroller.v5.adapter;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView modelView = new ModelView(viewName);
modelView.setModel(model);
// model은 어차피 넘어가면 컨트롤러에서 모델에 필요한 데이터 담는다.
return modelView;
}
private static 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;
}
}
프론트 컨트롤러 V5
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
// 이전 코드
//private Map<String, ControllerV4> controllerMap = new HashMap<>()
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5(){
// 1. Mapping 정보 주입
initHandlerMappingMap();
// 2. HandlerAdapter 정보 주입
initHandlerAdapters();
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
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());
// V4 컨트롤러 추가
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());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. handlerMappingMap 꺼내기
Object handler = getHandler(request);
if(handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyHandlerAdapter handlerAdapter = getHandlerAdapter(handler);
ModelView mv = handlerAdapter.handle(request, response, handler);
String viewName = mv.getViewName();
MyView myView = viewResolver(viewName);
myView.render(mv.getModel(), request, response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter handlerAdapter : handlerAdapters) { // 핸들러 다 뒤짐
if(handlerAdapter.supports(handler)){ // 서포트 호출 -> V3 핸들러 처리 가능?
return handlerAdapter; // 가능하면 그 어댑터를 반환
}
}
throw new IllegalArgumentException("handler adapter not found");
}
private Object getHandler(HttpServletRequest request){
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
'스프링 공부 (인프런 김영한 선생님) > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 웹 MVC 1편] 17. 스프링 MVC - 핸들러 매핑과 어댑터 / 뷰 리졸버 (0) | 2023.05.29 |
---|---|
[스프링 웹 MVC 1편] 16. 스프링 MVC - V1 ~ V5와 비교했을 때 (구조 이해) (0) | 2023.05.29 |
[스프링 웹 MVC 1편] 14. MVC 프레임워크 제작해보기 - v4 (0) | 2023.05.23 |
[스프링 웹 MVC 1편] 13. MVC 프레임워크 제작해보기 - v3 (0) | 2023.05.22 |
[스프링 웹 MVC 1편] 12. MVC 프레임워크 제작해보기 - v2 (0) | 2023.05.22 |