22.08.09
단순하고 실용적인 컨트롤러 - v4
v3 컨트롤러는 서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 등, 잘 설계된 컨트롤러이다. 그런데 실제 컨트톨러 인터페이스를 구현하는 개발자 입장에서 보면, 항상 ModelView 객체를 생성하고 반환해야 하는 부분이 조금은 번거롭다. 좋은 프레임워크는 아키텍처도 중요하지만, 그와 더불어 실제 개발하는 개발자가 단순하고 편리하게 사용할 수 있어야 한다. 소위 실용성이 있어야 한다.
=> '개발자 입장에서의 편리하게'는 프레임워크 입장에서는 복잡해질수도

기본적인 구조는 V3와 같다. 대신에 컨트롤러가 ModelView 를 반환하지 않고, ViewName 만 반환한다.
@Override
public String process(Map<String, String> paramMap, Map<String, Object>
model) {
return "new-form";
}
ModelView { Map<String, Object> model, String viewName } 를 해체
Map<String, Object> model은 빈 HashMap 레퍼런스를 파라미터로 넘기고 컨트롤러에서 viewName을 반환한다.
중요한 사실은 여기까지 한번에 온 것이 아니라는 점이다. 프레임워크가 점진적으로 발전하는 과정 속에서 이런 방법도 찾을 수 있었다.
프레임워크나 공통 기능이 수고로워야 사용하는 개발자가 편리해진다.
유연한 컨트롤러1 - v5
지금까지 코드에서는 프론트 컨트롤러가 한가지 방식의 컨트롤러 인터페이스만 사용할 수 있다.
예를 들면, 아래 코드를 포함하는 프론트 컨트롤러는 ControllerV4 인터페이스를 구현한 컨트롤러만 제어한다.
private Map<String, ControllerV4> controllerMap = new HashMap<>();
어댑터 패턴
어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자.

- 핸들러 어댑터: 중간에 어댑터 역할을 하는 어댑터가 추가되었는데 이름이 핸들러 어댑터이다. 여기서 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다.
- 핸들러: 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다. 그 이유는 이제 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다.
*실행 순서 요약*
1. RequestURI 를 통해 핸들러(컨트롤러)를 매핑
2. 어댑터에서 해당 핸들러가 어떤 버전의 컨트롤러 인터페이스인지 확인 후, 캐스팅 하여 컨트롤러의 process 메서드 호출
process의 반환 값이 ModelView가 아닌 컨트롤러라도 어댑터가 ModelView를 생성하여 반환 (어댑터의 역할)
3. 프론트 컨트롤러에서는 어댑터가 반환한 ModelView 로 뷰 렌더링 작업 호출
[ interface MyHandlerAdapter ]
boolean supports(Object handler)
: handler는 컨트롤러를 말한다. 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드다.
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
: 어댑터는 실제 컨트롤러를 호출하고, 그 결과로 ModelView를 반환해야 한다.
실제 컨트롤러가 ModelView를 반환하지 못하면, 어댑터가 ModelView를 직접 생성해서라도 반환해야 한다.
이전에는 프론트 컨트롤러가 실제 컨트롤러를 호출했지만 이제는 이 어댑터를 통해서 실제 컨트롤러가 호출된다.
참고: 컨트롤러(Controller) -> 핸들러(Handler)
이전에는 컨트롤러를 직접 매핑해서 사용했다. 그런데 이제는 어댑터를 사용하기 때문에, 컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면, 어떤 것이라도 URL에 매핑해서 사용할 수 있다. 그래서 이름을 컨트롤러에서 더 넒은 범위의 핸들러로 변경했다.
private final Map<String, Object> handlerMappingMap = new HashMap<>();
매핑 정보의 값이 ControllerV3 , ControllerV4 와 같은 특정 인터페이스에서 아무 값이나 받을 수 있는 Object 로 변경함
어댑터 패턴을 추가하는 것은 어렵지만, 기능 확장은 간편하다.
여기에 애노테이션을 사용해서 컨트롤러를 더 편리하게 발전시킬 수도 있다. (ex.스프링 MVC)
만약 애노테이션을 사용해서 컨트롤러를 편리하게 사용할 수 있게 하려면 어떻게 해야할까?
바로 애노테이션을 지원하는 어댑터를 추가하면 된다!
[ 프론트 컨트롤러 - (어댑터) - 애노테이션 기반 컨트롤러 ]
다형성과 어댑터 덕분에 기존 구조를 유지하면서, 프레임워크의 기능을 확장할 수 있다.
++핸들러 매핑 정보나 뷰 리졸버까지 인터페이스로 만들어서 구현체만 갈아끼우도록 확장 가능
스프링 MVC는 지금까지 학습한 내용과 거의 같은 구조를 가지고 있다.
스프링 MVC - 구조 이해
스프링 MVC 전체 구조

직접 만든 프레임워크 -> 스프링 MVC 비교
구조는 완전히 같다.
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping
- MyHandlerAdapter -> HandlerAdapter
- ModelView -> ModelAndView
- viewResolver -> ViewResolver
- MyView -> View
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
스프링 MVC의 프론트 컨트롤러. 스프링 MVC의 핵심
DispacherServlet 도 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.
스프링 부트는 서버를 띄울 때 DispacherServlet 을 서블릿으로 자동으로 등록(@WebServlet) 하면서 모든 경로( urlPatterns="/" )에 대해서 매핑한다. 즉, 어떤 경로이든 우선 매핑되도록!
참고: 더 자세한 경로가 우선순위가 높다. 그래서 기존에 등록한 서블릿도 함께 동작한다.
요청 흐름
서블릿이 호출되면 HttpServlet 이 제공하는 serivce() 가 호출된다.
스프링 MVC는 DispatcherServlet 의 부모인 FrameworkServlet 에서 service() 를 오버라이드 해두었다. FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch() 가 호출된다.
동작 순서
1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.
4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.
5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6. viewResolver 호출: 뷰 리졸버를 찾고 실행한다.
--JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.
7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
--JSP의 경우 InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 로직이 있다.
--타임리프의 경우 내부 렌더링 코드를 통해 HTML을 직접 렌더링 해서 reponse 객체에 담아 반환한다.
8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.
인터페이스 살펴보기
스프링 MVC의 큰 강점은 DispatcherServlet 코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다. 지금까지 설명한 대부분을 확장 가능할 수 있게 인터페이스로 제공한다. 이 인터페이스들을 구현해서 DispatcherServlet 에 등록하면 직접 컨트롤러를 만들 수 있다.
그러나 애플리케이션 개발 중 필요한 대부분의 기능은 이미 스프링에 구현되어 있기 때문에 직접 만들 일은 거의 없다.
주요 인터페이스 목록
- 핸들러 매핑: org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버: org.springframework.web.servlet.ViewResolver
- 뷰: org.springframework.web.servlet.View