본문 바로가기
Spring/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

핸들러 매핑, 핸들러 어댑터, 뷰 리졸버

by hk27 2022. 1. 27.
핸들러 매핑, 핸들러 어댑터, 뷰 리졸버에는 어떤 것들이 있고 어떻게 사용될까요?

 

안녕하세요.

오늘은 스프링 MVC 구조 중에서 핸들러 매핑, 핸들러 어댑터, 뷰 리졸버를 자세히 알아보겠습니다.

 

지난 게시글에서 스프링 MVC의 전체적인 구조를 알아보았는데요,

궁금하신 분은 아래 게시글을 참고해주세요. 

https://passionate.tistory.com/43

 

스프링 MVC 구조

스프링 MVC는 어떤 구조로 이루어져 있을까요? 안녕하세요! 오늘은 스프링 MVC의 구조를 알아보겠습니다. 앞서 MVC 프레임워크를 점진적으로 만들어보았습니다. 관심 있으신 분들은 아래 링크를

passionate.tistory.com

 

스프링 MVC의 구조는 아래와 같습니다.

핸들러 매핑, 핸들러 어댑터, 뷰 리졸버를 자세히 알아봅시다.

 

핸들러 매핑

핸들러 매핑은 말 그대로 요청에 맞는 핸들러를 찾는 것을 의미합니다. 

위 사진에서 볼 수 있듯이 디스패처 서블릿은 HTTP 요청이 들어오면 가장 먼저 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회합니다. 

 

스프링 부트가 자동 등록하는 핸들러 매핑 여러 개 중 2개를 살펴봅시다.

0 = RequestMappingHandlerMapping : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러 찾음

 

요청이 들어오면 순서대로 핸들러 매핑을 불러서 처리할 수 있는 핸들러를 찾고, 만약 없으면 다음 순서로 넘어갑니다.

스프링 빈으로 핸들러를 찾는 BeanNameUrlHandlerMapping을 사용해봅시다.

이는 요청 URL과 같은 이름을 갖는 스프링 빈 핸들러를 찾아서 반환합니다.

 

예시 코드를 봅시다.

@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는 "/springmvc/old-controller" 이라는 이름으로 스프링 빈에 등록되어있습니다. 

 

디스패처 서블릿은 "/springmvc/old-controller" URL의 요청이 들어오면 적절한 핸들러를 찾기 위해 핸들러 매핑을 우선순위대로 수행합니다.

먼저 RequestMappingHandlerMapping을 실행하지만 적절한 핸들러를 찾지 못하고, 다음으로 BeanNameUrlHandlerMapping을 수행합니다. URL과 같은 이름의 핸들러인 OldController가 있으므로 이를 반환합니다.

 

 

핸들러 어댑터

핸들러 매핑을 이용해 적절한 핸들러를 찾았으니 핸들러를 실행할 수 있는 핸들러 어댑터를 찾아야 합니다.

예를 들어서 위의 OldController는 Controller 인터페이스의 구현 클래스입니다.

Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾아서 실행할 수 있습니다.

 

핸들러 매핑처럼 핸들러 어댑터도 몇 개를 스프링 부트가 자동 등록해둡니다.

그 중 많이 사용하는 3개를 알아봅시다.

0 = RequestMappingHandlerAdapter : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter: HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter: Controller 인터페이스 처리

 

핸들러 어댑터도 핸들러 매핑과 같이 순서대로 수행하고, 실패하면 다음 순서로 넘어갑니다.

0번인 RequestMappingHandlerApater부터 supports()를 순서대로 호출해서 true가 반환되면 핸들러 어댑터로 사용합니다.

위의 OldController는 Controller 인터페이스의 구현 클래스이므로 2번 핸들러 어댑터인 SimpleControllerHandlerAdapter가 처리합니다.

 

1번 핸들러 어댑터인 HttpRequestHandlerAdpater를 사용하는 핸들러는 HttpRequestHandler를 상속받습니다.

예시 코드를 봅시다.

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

위의 코드는 HttpRequestHandlerAdapter가 처리합니다. 

 

핸들러 매핑에서 가장 우선순위가 높은 RequestMappingHandlerMapping와 핸들러 어댑터에서 가장 우선순위가 높은 RequestMappingHandlerAdapter는 @RequestMapping에서 사용합니다. 

실제로는 이 방식의 컨트롤러가 99.9% 사용되는데요. 이 방식은 다음 게시글에서 알아보겠습니다.

 

 

뷰 리졸버

다음으로 뷰 리졸버를 자세히 알아봅시다.

뷰 리졸버는 뷰의 논리 이름을 받아서 물리 주소를 갖는 뷰 객체를 반환하는 역할을 합니다.

앞서 MVC 프레임워크를 만들 때, vIewResolver를 메소드로 아래와 같이 구현하였습니다. (https://passionate.tistory.com/41)

private MyView viewResolver(String viewName){
    return new MyView("/WEB-INF/views/"+viewName+".jsp");
}

 

스프링 MVC에서는 뷰 리졸버가 인터페이스로 제공됩니다. 

스프링 부트가 자동으로 등록하는 뷰 리졸버 여러 개 중 2개는 아래와 같습니다.

1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다.
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.

 

BeanNameViewResolver는 뷰 이름과 동일한 이름을 가진 스프링 빈 객체를 뷰 객체로 사용하는데, 주로 커스텀 뷰 클래스를 만들어서 엑셀 파일을 생성하고 다운받는 등의 기능에 사용합니다[1].

저희는 두 번째인 InternalResourceViewResolver를 사용할 것입니다.  

InternalResourceViewResolver는 뷰 이름을 받으면 prefix와 suffix를 추가해서 물리 주소를 찾아 뷰 객체를 리턴합니다.

 

뷰를 사용하려면 컨트롤러에서 뷰의 논리 이름을 넘겨야 합니다.

아래 코드는 ModelAndView 객체를 넘겨서 뷰의 논리 이름을 전달하는 예시입니다. 

@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");
    }
}

이처럼 new-form이라는 논리 이름이 전달되면 뷰 리졸버를 순서대로 호출합니다.

먼저 BeanNameViewResolver가 수행되지만, new-form 이라는 이름의 스프링 빈으로 등록된 뷰가 없으므로 InternalResourceViewResolver가 호출되고, 앞뒤로 prefix와 suffix를 추가해 물리 주소를 찾아 뷰를 반환합니다. 

 

참고로 prefix와 suffix는 resources/application.properties 파일에 아래와 같이 등록할 수 있습니다. 

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

 

참고로 최근에는 JSP보다 타임리프를 많이 사용하는데 타임리프 리졸버는 없는지 궁금하실 수 있습니다.

타임리프를 사용하면 TymeleafViewResolver를 등록해야 합니다. 라이브러리를 추가하면 스프링 부트가 자동으로 등록합니다. 

 

 

지금까지 핸들러 매핑, 핸들러 어댑터, 뷰 리졸버에 어떤 것이 있고, 어떻게 사용되는지 알아보았습니다.

다음 시간에는 본격적으로 스프링 MVC를 다뤄보겠습니다. 

 

 

 

인프런  '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

[0] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술, 섹션 5. 스프링 MVC - 구조 이해 https://www.inflearn.com/course/스프링-mvc-1

[1] https://snoopy81.tistory.com/325

 

 

 

 

댓글