MVC?
MVC(Model - View - Controller)는 소프트웨어 공학에서 사용되는 디자인패턴이고, 스프링에서도 이를 웹 모듈로 지원한다.
Model
모델은 애플리케이션의 정보나 데이터 등 뷰가 필요한 데이터를 담는다.
View
뷰는 사용자에게 보여지는 화면을 모델의 데이터를 사용해 렌더링한다.
Controller
컨트롤러는 요청을 위임해서 비즈니스 로직과 데이터를 모델에 담아 뷰로 넘겨준다.
뷰와 모델은 서로는 모르지만 양쪽을 둘다 아는 컨트롤러는 통해 소통하며, 데이터와 비즈니스 로직 사이의 동작을 관리하고, View 와 Model은 서로 직접 소통이 아닌 Controller를 통해 소통한다.
Spring Web MVC 구조
DispatcherServlet?
Client에서 WAS를 통해 받은 Request 처리하는 Servlet이다.
Spring MVC를 사용하지 않으면 Servlet 설정파일, Servlet 클래스를 각각 설정해줘야하지만 Spring MVC를 활용하면 HttpServlet을 상속받은 DispatcherServlet이 Spring IoC Container에 접근해서 Controller에 접근할 수 있게 된다.
위의 그림은 아래의 DispatcherServlet.doDispatch() 실행시에 일어나는 순서도이다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 1. 핸들러 조회
mappedHandler = this.getHandler(processedRequest);
// 2. 핸들러 어댑터 조회
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// apply PreHandler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 3. 핸들러 어댑터 실행
// 4. 핸들러 어댑터 - > 컨트롤러에 위임
// 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 6. viewResolver 호출
this.applyDefaultViewName(processedRequest, mv);
// apply PostHandler
mappedHandler.applyPostHandle(processedRequest, response, mv);
// Result call
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
}
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
String defaultViewName = this.getDefaultViewName(request);
if (defaultViewName != null) {
// 7. View (논리적 View -> 물리적 View)
mv.setViewName(defaultViewName);
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
// View Rendering 호출
this.render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
String viewName = mv.getViewName();
View view;
if (viewName != null) {
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
} else {
view = mv.getView();
}
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
핸들러 조회, 핸들러 어댑터
// 핸들러 조회
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
핸들러 맵핑을 통해 Request URL에 매핑된 컨트롤러 주소의 유무 조회한다. 맵핑 가능한 컨트롤러를 찾고 바로 호출하는게 아닌 핸들러 어댑터를 통해 컨트롤러에게 위임하는 이유는 컨트롤러의 파라미터, 리턴타입이 모두 다르기 때문에 디스패쳐서블릿과 컨트롤러 사이에 Adapter 패턴을 적용해 반환값을 ModelAndView 객체로 응답을 통일한다.
핸들러 어댑터와 핸들러(컨트롤러) 사이의 preHandler, postHandler
요청을 컨트롤러에 위임하고 위임 전후로 preHandler, postHandler를 통해 Interceptor -> ArgumentResolver를 거친다. 컨트롤러 전후에 필요한 작업을 Interceptor, ArgumentResolver에서 처리한다. 모델, @ModelAttribute, @RequestParam 등의 정보를 HTTP Request에서 읽어 객체로 만든 뒤 컨트롤러에 넣어주는 역할을 ArgumentResolver에서 HTTP 메시지 컨버터를 호출해 처리한다.
ViewResolver 및 View Render
핸들러 어댑터로 받은 ModelAndView에 해당하는 View를 찾고 모델의 정보로 렌더링시켜 클라이언트에게 넘겨준다.
Spring Web MVC를 이용한 Restfull API
핸들러 어댑터 실행 전후로 요청되는 ArgumentResolver에서 HTTP 메시지 컨버터를 호출할 때 @RequestBody, @ResponseBody 등의 어노테이션이 붙어있으면 직렬화, 역직렬화를 통해 데이터를 처리하고 ModelAndView를 반환하는 것이 아니라 바로 HTTP 응답을 내보낸다.
이때 Jackson의 ObjectMapper로 직렬화, 역직렬화를 진행하고 이때 reflection API를 사용하므로 요청으로 들어오는 JSON 객체는 역직렬화해서 컨트롤러로 받고, 응답으로 나가는 객체는 메모리의 객체에서 통신을 위한 JSON으로 바꾸는 직렬화하기 위해 기본 생성자가 필요하다.
참고
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard