핸들러 매핑과 핸들러 어댑터

바그다드·2023년 4월 22일
0
  • 프로젝트를 진행하면서 대략적인 스프링의 MVC구조를 파악하고 있다고 생각했지만, 핸들러 매핑과 핸들러 어댑터의 차이와 핸들러 어댑터가 왜 필요한지에 대한 것은 잘 모르고 있었다. 따라서 이번에는 핸들러 매핑과 핸들러 어댑터에 대해 알아보려고 한다.

Spring MVC구조

  • 먼저 Spring MVC구조를 확인해보자
  • 동작 순서
  1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
  2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.
  4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.
  5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서
    반환한다.
  6. viewResolver 호출: 뷰 리졸버를 찾고 실행한다.
    JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.
  7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를
    반환한다.
    JSP의 경우 InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 로직이 있다.
  8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.

핸들러 어댑터

  • 순수 자바를 이용해서 프로젝트를 진행했을 때는 클라이언트의 요청 url과 매칭되는 컨트롤러를 찾고, 그 컨트롤러를 인터페이스에 담아 사용했다. 이 때는 MVC버전을 통일해서 작업하여 따로 핸들러 어댑터가 필요하지 않았는데, 하나의 프로젝트에서도 여러 버전의 MVC패턴을 사용해야 한다고 하면, 컨트롤러에도 넘겨줘야할 매개변수와 컨트롤러의 리턴 타입이 달라지는 문제가 생긴다.


    이건 하나의 예시인데 V3이냐 V4이냐에 따라 컨트롤러에서 필요로 하는 매개변수도 다르고, 리턴 타입도 다 달라지게 된다.
  • 이처럼 dispatcher servlet과 컨트롤러 사이에서 각 버전에 맞는 컨트롤러에 연결하고, 리턴 타입을 맞춰주는 역할을 핸들러 어댑터가 담당한다.
  • 그럼 이제 핸들러 어댑터 코드를 보면서 그 역할을 확인해보자

HandlerAdapter V3

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 ServerException, IOException {
        ControllerV3 controller = (ControllerV3) handler;

        Map<String, String> paramMap = createParamMap(request);
        ModelView modelView = controller.process(paramMap);
        return modelView;
    }



    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;
    }

}

HandlerAdapter V4

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 ServerException, IOException {
        ControllerV4 controller = (ControllerV4) handler;

        Map<String, String> paramMap = createParamMap(request);
        HashMap<String, Object> model = new HashMap<>();
        String viewName = controller.process(paramMap, model);

        ModelView mv = new ModelView(viewName);
        mv.setModel(model);

        return mv ;
    }



    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;
    }

}
  • supports라는 메서드를 이용해 요청한 url과 매칭되는 컨트롤러가 핸들러 어댑터가 처리할 수 있는 인터페이스의 구현체인지 먼저 확인을 한다.
  • 맞다면 handle메서드를 통해 해당 컨트롤러의 로직을 수행하고, 그 반환타입을 받아 ModelView를 생성하여 반환하는데, 여기서 주목해야 할 것은 handle메서드 부분이다.
    v3 : ModelView modelView = controller.process(paramMap);
    v4 : String viewName = controller.process(paramMap, model);
    이 부분은 컨트롤러의 로직을 수행하고 그 반환값을 돌려받는 부분인데, 컨트롤러로 넘겨주는 매개변수고 다르고, 컨트롤러로부터 돌려받는 반환 타입도 다르다.
  • 이처럼 핸들러 어댑터는 MVC 버전에 따라 달라지는 매개변수를 컨트롤러에 맞게 처리해주고, 동일한 형태로 반환 값을 돌려준다. 이는 객체 지향 원칙 중 OCP를 지킬 수 있게 하는데, 디스패처 서블릿(프론트 컨트롤러)의 입장에서는 요청한 컨트롤러의 버전이 무엇인지 전혀 상관 없이 컨트롤러가 어댑터에 요청만 하면 어댑터가 알아서 매개변수와 반환 타입을 조정해서 처리해주기 때문이다.

핸들러 매핑과 핸들러 어댑터의 차이

  • 핸들러 매핑
    핸들러 매핑은 요청 url과 매칭되는 컨틑롤러를 찾아주는 역할을 한다.
  • 핸들러 어댑터
    핸들러 매핑에서 찾아준 컨트롤러를 어떤 어댑터가 처리할 수 있는지 확인하고, 컨트롤러에 필요한 매개변수를 생성해 값을 넘겨주고, 리턴 값을 ModelAndView로 일치시켜 반환해준다.
    - 여기서 말하는 ModelAndView는 MVC패턴에서 Model과 View를 뜻한다.
profile
꾸준히 하자!

0개의 댓글