![[Spring] 스프링 프레임워크 동작 과정, 서블릿, 핸들러, 어댑터, 뷰리졸버](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxRW6J%2FbtspeKealwu%2FNckwvnAuKwGhUioWIKuro1%2Fimg.png)
김영한 님 MVC1 강의 정리본입니다.
전체 구조
1. 핸들러 매핑 정보
클라이언트(사용자)가 웹페이지에 접속을 하면 Front Controller가 해당 요청을 받는다. 가장 먼저 사용자가 접속한 URL을 처리할 수 있는 handler(핸들러)를 찾는다. 핸들러 매핑 정보에는 URL과 handler가 매핑되어 있다. 즉 URL 별로 handler가 여러 개 존재한다.
ex) localhost:8080/front-controller/v5/v4/members/new-form로 접속하면 MemberFormControllerV4 핸들러가 해당 요청 URL을 처리하기 위해 사용된다.
// 핸들러 매핑 정보를 담는 Map
private final Map<String, Object> handlerMappingMap = new HashMap<>();
// 매핑 정보 저장 (url, handler)
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/new-form",
new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/save",
new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members",
new MemberListControllerV4());
}
2. 핸들러 어댑터 목록
/front-controller/v5/v4/new-form의 요청을 처리하기 위해 MemberFormControllerV4 핸들러를 사용한다고 가정한다. MemberFormControllerV4는 다음과 같이 정의되어 있다.
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model)
throws ServletException, IOException;
}
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model)
throws ServletException, IOException {
return "new-form";
}
}
MemberFormControllerV4 핸들러를 처리할 수 있는 핸들러 어댑터를 찾아야 한다. 핸들러 어댑터 목록에 2개의 핸들러 어댑터가 저장되어 있다. ControllerV3와 ControllerV4 인터페이스를 구현한 핸들러를 처리할 수 있는 핸들러 어댑터 2개이다.
// 핸들러 어댑터 목록
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
// 핸들러 어댑터 목록에 저장된 핸들러 어댑터
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
핸들러 어댑터는 다음과 같이 구현되어 있다. supports 메서드와 handle 메서드가 존재한다. supports 메서드만 집중적으로 보자. supports 메서드의 handler 매개변수를 보자. 이 매개변수를 instanceof 연산자를 통해 형변환이 가능한지를 확인한다. 가능하다면 매개변수로 넘어온 handler를 처리할 수 있는 핸들러 어댑터라는 것이다.
// 핸들러 어댑터 인터페이스
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException;
}
// ControllerV3 핸들러를 처리하는 어댑터
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 {
// ..생략
}
}
// ControllerV4 핸들러를 처리하는 어댑터
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 {
// .. 생략
}
}
getHandlerAdapter의 내부를 보자. handlerAdapters에 저장된 어댑터를 하나씩 꺼내어 supports 메서드를 실행하고 있다. 우리는 /front-controller/v5/v4/new-form에 접속했고 MemberFormControllerV4 핸들러를 처리할 핸들러 어댑터를 찾고 있다. MemberFormControllerV4 핸들러의 경우 ControllerV4 인터페이스를 구현하였기 때문에 ControllerV4HandlerAdapter 어댑터가 해당 핸들러를 처리하게 될 것이다.
(ControllerV4HandlerAdapter 어댑터의 supports() 메서드의 매개변수로 MemberFormControllerV4 핸들러를 넘기면 true임, MemberFormControllerV4 핸들러가 ControllerV4 인터페이스를 구현했기 때문.)
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for(MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("핸들러를 처리할 어댑터가 존재하지 않습니다 " +
"handler = " + handler);
}
3. 핸들러 어댑터
2번에서 핸들러 어댑터를 찾았으니 이제 핸들러를 실행하면 된다. 이때 핸들러 어댑터의 handle 메서드가 실행된다.
메서드 내부를 자세하게 이해하려고 하지 않아도 된다. handle 메서드만 집중적으로 보자. handle 메서드를 보면 controller.process()를 통해 핸들러를 실행시키고 VIew Name을 반환하고 있다. 이 View Name은 클라이언트에게 보여줄 뷰 이름이다. 해당 뷰 이름을 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);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView view = new ModelView(viewName);
view.setModel(model);
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;
}
}
4. ViewResolver
ViewResolver는 뷰의 논리적 이름을 물리적 경로로 변환하여 준다. 핸들러 어댑터가 핸들러를 실행하면서 ModelView를 반환했었다. 이때 ModelView에는 뷰의 이름을 저장했었는데 이때 뷰의 이름은 논리적인 뷰 이름이다. 논리적인 뷰 이름이란 클라이언트에게 보여줄 뷰의 경로를 제외한 뷰의 이름만 담고 있다.
어쨌든 클라이언트가 요청한 것에 대한 뷰를 보여주기 위해서는 전체 경로를 알아야 한다. 이때 ViewResolver가 작동하는데 다음과 같이 실행된다.
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
5. View
View 객체는 최종적으로 사용자에게 뷰를 응답해 준다. 이때 render 메서드가 실행된다. 참고로 RequestDispatcher의 forward는 서버 내부적으로 servlet에서 jsp를 호출한다.
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
6. 클라이언트
View가 응답으로 보내준 View를 아래와 같이 받을 수 있다.
결론
위는 스프링 프레임워크 동작 과정을 이해하기 위해 만들어진 객체들일뿐 실제로 스프링에서 사용되는 객체가 아니다.
스프링 프레임워크에서 실제로 사용되는 객체는 다음과 같다.
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping
- MyHandlerAdapter -> HandlerAdapter
- ModelView -> ModelAndView
- MyView -> View
- viewResolver 메서드 -> ViewResolver 인터페이스