지금까지 만든 MVC 프레임워크와 실제 Spring 프레임워크를 비교해보자.
지금까지의 구조를 잘 정리하고 이해했다면 우리가 만든 구조와 다른점이 거의 없다는 것을 알 수 있다. 위 그림과 아래 그림의 차이는 FrontController라는 이름 대신 DispatcherServlet이란 이름 차이일뿐 둘다 Servlet을 상속받아서 사용되는것도 똑같다. 그럼 실제 코드로 어떻게 동작하는 구조인지도 살펴보자
Dispatcher Servlet 동작 구조
Spring MVC도 프론트 컨트롤러 패턴으로 구현되어 있다. Spring의 프론트 컨트롤러가 바로 DispatcherServlet이다. 그리고 이 DispatcherServlet의 부모 클래스도 HttpServlet을 상속받아서 사용되고 동작된다. 자세한 구조는 실제로 코드를 확인해보길 바란다. 실제 Spring boot는 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로("/*")에 대해 매핑하고 있다.
요청 흐름
서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
service()를 시작으로 여러 메서드들이 호출되는데 그 중 doDispatcher()가 호출된다. 그리고 이 doDispatcher()가 핵심코드이며 이 코드를 분석해보자.
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
}
우리가 이전에 만들었던 MVC 프레임워크에서 핸들러를 조회하고 반환받는 getHandler()가 보일것이다 이 핸들러를 통해 컨트롤러의 정보를 가져오고 핸들러에서 가져온 컨트롤러 정보로 adapter를 조회하여 어댑터를 가져오고 반환 받은 어댑터를 통해 handle()을 실행하여 ModelAndView를 반환 받아서 해당 객체로 process를 진행한다.
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView
mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request,
HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
그리고 해당 코드도 우리가 익숙한 코드이다. processDispatchResult()는 반환 받은 ModelAndView를 통해 render()를 실행하고 render()는 뷰 리졸버를 통해서 View 객체를 찾고 View를 반환 받아 해당 view를 실제 view로 전달하는 역할까지 수행한다.
코드를 분석해보면 Spring MVC의 큰 강점은 DispatcherServlet의 코드 변경 없이 원하는 기능을 확장할 수 있다는 점이다. 지금까지 설명한 대부분 기능을 인터페이스로 제공하고 있어 해당 인터페이스를 상속하여 확장하여 사용이 가능하다.
핸들러 매핑과 핸들러 어댑터
현재 @Controller로 많이 사용하고 있지만 과거에는 @Controller대신 인터페이스의 Controller를 사용했다. 해당 방법으로 어떻게 동작했었는지 확인해보자.
org.springframework.web.servlet.mvc.Controller
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
해당 코드와 같이 Controller를 구현했었야했는데. 동작하는 구조는
뷰 리졸버
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return new ModelAndView("new-form");
}
}
위의 OldController에서 null을 반환하던 곳을 ModelAndView를 실제로 반환하도록 수정하여 뷰 정보를 넣어보자.
우리는 자원의 view 이름을 입력해줬지만 웹 브라우저에는 아직 WhiteLable Error Page가 나올것이다. 이는 prefix와 suffix 값이 입력되지 않아서 그런것인데. 실제 new-form이란 view의 자원은 /WEB-INF/views/new-form.jsp 파일이기 때문이다. 그리고 이 정보는 java로 입력이 가능한데 InternalResourceViewResolver
스프링 컨테이너의 빈을 등록하는 방법으로 등록하는 방법도 있고 그 방법은 인터넷을 찾아보기 바란다! 우리는 간단하게 application.properties 파일에 추가로 등록해주려 한다.
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
위 코드를 application.properties에 추가로 입력해주면 이제 정상적으로 페이지를 찾는 것을 확인할 수 있다. 이 방법으로 통해 반환하는 new ModelAndView("/WEB-INF/views/new-form.jsp")로 반환해도 실제 동작하긴 하는데 공통으로 처리해야할 부분을 각각 처리하게 되므로 권장하는 방법은 아니다.
뷰 리졸버의 동작 구조는