본 포스팅은 김영한 강사님의 인프런 강의 "스프링 MVC 1편"을 정리한 포스팅으로 강의 자료에서 사용한 자료를 사용했음을 밝힙니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
https://growth-coder.tistory.com/119
이전 포스팅에서 김영한 강사님의 controller v1부터 v4까지 발전시키는 과정을 공부했었다.
여기에 추가로 어댑터 패턴을 적용한 controller v5의 구조를 정리해보려한다.
용어
- 핸들러 : 컨트롤러보다 더 넓은 의미.
- 핸들러 어댑터 : 어댑터.
김영한 강사님의 controller v3, controller v4를 기준으로 설명을 해보면 controller v3의 경우 안에 viewName과 model이 담겨 있는 ModelView를 반환하고 controller v4의 경우 viewName만 반환한다.
controller v3, v4는 모두 인터페이스이고 이러한 인터페이스를 구현하고 있는 여러 종류의 컨트롤러가 존재한다.
그런데 controller v3를 구현한 컨트롤러도 써야하고 controller v4를 구현한 컨트롤러도 써야할 때 문제가 발생한다.
둘이 반환 타입이 다르기 때문에 이전에 구현했던 방식으로는 둘 중 하나밖에 못 쓰는 점이다.
그래서 이를 해결하기 위해 어댑터 패턴이 등장한다.
위 그림에서 핸들러 어댑터를 살펴보면 프론트 컨트롤러에게 ModelView를 반환하는 모습을 확인할 수 있다.
이 핸들러 어댑터는 핸들러(컨트롤러)로부터 반환받는 값을 모두 ModelView로 바꾸어 프론트 컨트롤러에게 반환하는 역할을 한다.
예를 들어 controller v3에 관한 핸들러 어댑터는 핸들러로부터 ModelView를 반환받기 때문에 이 값을 그대로 반환하면 된다.
그에 비해 controller v4에 관한 핸들러 어댑터는 핸들러로부터 viewPath만 반환받기 때문에 ModelView를 새로 생성하여 이 안에 model과 viewPath를 넣어서 ModelView를 반환하는 것이다.
이렇게 어댑터 패턴을 사용하면 반환 값이 다른 컨트롤러를 여러 개 다룰 수 있게 된다.
반환 값이 다른 핸들러가 3개 있다고 하면 이에 해당하는 어댑터 또한 3개가 존재하게 된다.
어댑터의 뜻 그대로 연결이 되도록 타입을 변경한다고 보면 된다.
어댑터의 구조는 다음과 같다.
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) {
ControllerV3 controller = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(request);
//createParamMap은 HttpServletRequest의 파라미터들의 정보가 담긴 메소드. 생략.
ModelView mv = controller.process(paramMap);
return mv;
}
}
supports 메소드는 인자로 컨트롤러를 받아서 이 컨트롤러를 변환하기 위한 어댑터가 맞는지 확인한다.
handle 메소드는 인자로 이 어댑터에 해당하는 컨트롤러를 받아서 컨트롤러의 process 메소드를 실행하고 ModelView를 반환한다.
만약 controller v4에 관한 어댑터라면 viewName만 받아서 ModelView를 새로 생성하는 과정이 추가될 것이다.
이렇게 어댑터 패턴을 적용할 경우 프론트 컨트롤러의 모습은 다음과 같이 변하게 된다.
@WebServlet(name = "이름", urlPatterns = "요청 url ")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
initHandlerMappingMap(); //모든 핸들러(컨트롤러)를 map에 넣는 메소드. key가 url이고 value가 핸들러(컨트롤러)이다.
initHandlerAdapters(); //모든 핸들러 어댑터를 리스트에 넣는 메소드
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response){
.
.
.
}
}
가장 먼저 모든 핸들러(컨트롤러)를 map에 넣고 모든 핸들러 어댑터를 리스트에 넣는다.
핸들러를 넣는 map의 경우 key가 url이고 value가 핸들러(컨트롤러)이다.
요청 url에 따라서 그 url을 처리하는 핸들러(컨트롤러)를 가져오기 위함이다.
그리고 urlPatterns에 해당하는 url로 요청이 들어오면 service 함수가 자동으로 실행이 되는데 service 함수의 구현은 아래와 같다.
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
Object handler = getHandler(request);
//getHandler 메소드는 request의 url을 가져와서 해당 url을 다루는 핸들러를 반환하는 메소드.
if (handler == null) { //핸들러가 없으면 NOT FOUND 반환.
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyHandlerAdapter adapter = getHandlerAdapter(handler);
//getHandlerAdapter는 모든 핸들러 어댑터가 담긴 리스트에서 하나씩 어댑터를 가져와서
//어댑터의 supports 메소드의 인자에 handler를 넣었을 때 true를 반환하는 어댑터를 가져오는 메소드
ModelView mv = adapter.handle(request, response, handler);
MyView view = viewResolver(mv.getViewName());
view.render(mv.getModel(), request, response);
}
스프링 MVC의 경우 이러한 어댑터를 사용하여 여러 종류의 컨트롤러를 지원한다.
아래가 스프링 MVC의 구조이다. (Dispatcher Servlet은 Front Controller)
스프링 MVC의 핵심은 Dispatcher Servlet이다.
Dispatcher Servlet는 모든 url(urlPatterns = "/")에 대해 매핑되어있고 스프링 부트가 자동으로 이 서블릿을 등록한다.
Dispatcher Servlet도 HttpServlet을 상속받기 때문에 모든 url 요청에 대해서 service를 호출한다.
이외에도 여러 메소드들을 호출하는데 핵심은 Dispatcher Servlet의 doDispatcher 메소드이다.
이 메소드가 다음과 같이 우리가 구현한 어댑터 패턴과 거의 비슷하게 동작한다.
- 핸들러 조회
- 핸들러 어댑터 조회
- 핸들러 어댑터 실행하면 핸들러가 실행되고 최종적으로 ModelAndView 반
- 뷰 리졸버를 통한 뷰 찾기
- 뷰 렌더
이렇게 어댑터 패턴이 적용되어 있기 때문에 여러 종류의 핸들러를 사용할 수 있는 것이다.
예를 들어 우리가 컨트롤러를 만드려고 할 때 만드는 방법은 여러가지가 존재한다.
1. Controller 인터페이스를 구현하기
@Component("/springmvc/old-controller")
public class OldController implements Controller {
.
.
.
}
2. HttpRequestHandler 인터페이스를 구현하기
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
.
.
.
}
3. 어노테이션 기반
@Controller
@RequestMapping("/api/members")
public class InterestController {
.
.
.
}
이렇게 다양한 방식이 존재하는데 스프링에서는 어댑터 패턴을 구현하기 때문에 컨트롤러의 종류가 다르더라도 그에 맞는 어댑터만 사용한다면 적용이 가능하다.
1, 2번 방식만 존재했을 시절에 3번 방식을 새롭게 적용한다고 하면 새로운 방식에 해당하는 어댑터만 만들면 쉽게 확장할 수 있는 것이다.
'공부 > Spring' 카테고리의 다른 글
[Spring][인프런 MVC 1편] 로그(logger) 사용법 (0) | 2023.03.06 |
---|---|
[Spring][인프런 스프링 MVC] 스프링 MVC HTTP 요청, 응답 여러가지 방법 (0) | 2023.03.05 |
[Spring][인프런 스프링 MVC] MVC 구를 직접 개선해가며 스프링 MVC 구조를 이해하기 (controller v1~v4) (0) | 2023.03.01 |
[Spring] 스프링 mysql 데이터베이스와 jpa 연동 (2) | 2023.02.17 |
[Spring][Thymeleaf] 공통 레이아웃 적용 (Thymeleaf Layout Dialect) (1) | 2023.02.14 |
댓글