오늘 이야기할 내용은 spring의 HandlerMapping, HandlerAdapter 인터페이스에 대해 알아보도록 하자.
spring web, webflux 비슷한 아키텍처로 동작하기 때문에 예제는 그냥 web으로 설명하도록 하겠다.

먼저 이글을 읽고 오는 것을 추천하지만 굳이 보지 않아도 된다.

HandlerMapping

이 인터페이스는 해당 요청 정보를 기준으로 어떤 컨트롤러를 사용할 것 인가를 결정하는 인터페이스이다. 간단히 말해서 해당 url로 해당 컨트롤러(핸들러)를 선택하는 기준이 되는 인터페이스이다.
HandlerMapping 인터페이스는 여러 구현체를 가지고 있는데 한개씩 살펴보도록 하자.

BeanNameUrlHandlerMapping

이 구현체는 클래스명 그대로 빈명을 url로 사용한다는 매핑 전략이다. 아주 심플하게 사용할 수 있는 매핑전략이므로 설정 또한 간단하다.

@Bean("/accounts")
public AccountController accountController() {
    return new AccountController();
}

빈명에 슬러쉬(“/”) 가 존재하면 매핑 전략에 대상이 된다. 실제로 저 url이 요청할 url이랑 매핑된다는 이야기이다.
위와 같이 설정하면 /accounts는 저 controller로 매핑된다는 것을 의미한다.

@Component("/accounts")
@Controller("/accounts")
public class AccountController {

}

사실 위와 같이 @Component 이나 @Controller 어노테이션 둘중 하나를 이용해도 된다.

위와 같이 BeanNameUrlHandlerMapping 을 사용하려면 설정을 해줘야 한다. 직접적으로 설정을 해줘도 무방하지만 @EnableWebMvc 어노테이션을 선언해주면 자동으로 BeanNameUrlHandlerMapping 클래스가 자동으로 빈으로 등록된다. xml 경우엔 <mvc:annotation-driven/>을 사용하면 된다. (사실 xml은 정확하지 않다.)
참고로 Spring boot는 사실 딱히 설정할 필요는 없다.

SimpleUrlHandlerMapping

이것역시 간단한 설정만으로 사용할 수 있다. 이 매핑은 url과 controller를 map에 담아 매핑할 수 있는 전략이다.

@Bean
SimpleUrlHandlerMapping urlHandlerMapping() {
    SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
    simpleUrlHandlerMapping.setOrder(Ordered.LOWEST_PRECEDENCE - 2);

    Map<String, Object> mapping = new HashMap<>();
    mapping.put("/accounts", accountController());
    simpleUrlHandlerMapping.setUrlMap(mapping);
//or 
//  Properties properties = new Properties();
//  properties.put("/accounts", "accountController");
//  simpleUrlHandlerMapping.setMappings(properties);

    return simpleUrlHandlerMapping;
}

Map 과 Properties 아무거나 사용해도 상관없고 value에는 인스턴스를 넣어도 되고, 빈 명을 입력해도 무방하다.

여기에서 order를 준 이유는 resourceHandlerMapping(기본 값 : Ordered.LOWEST_PRECEDENCE – 1) 보다 먼저 urlHandlerMapping이 먼저 동작해야 된다. 만약 order를 설정 하지 않았다면 resourceHandlerMapping 에는 모든 요청이 걸리게 되어 있다. 그래서 원하는 매핑으로 동작하지 않는다.

RequestMappingHandlerMapping

사실 우리는 해당 매핑 전략을 대부분 이용하고 있다. @RequestMapping 어노테이션을 사용해서 개발한다면 해당매핑전략을 사용하고 있다는 뜻이다.

@Controller
@RequestMapping("/accounts")
public class AccountController {

}

위와 같이 손쉽게 @RequestMapping (@Get, @Post…) 어노테이션을 사용하면 된다. 설정방법은 BeanNameUrlHandlerMapping와 동일하게 EnableWebMvc 어노테이션을 선언하거나 xml 경우엔 <mvc:annotation-driven/>을 선언하면 된다. 이것 역시 spring boot 경우엔 아무 설정할 필요는 없다.

여기까진 매핑 전략에 대해서 알아봤다. 사실 여기까진 어떤 url에 어떤 컨트롤러를 결정하는 것까지 만이다. 실제 어떤 메서드를 호출하는지는 HandlerMapping 에선 알 수 없다.
그래서 우린 HandlerAdapter 인터페이스를 알아야 한다.

HandlerAdapter

이 인터페이스는 HandlerMapping에서 결정된 핸들러 정보로 해당 메서드를 직접 호출해 주는 스펙이다. 이 역시 여러개의 구현체가 존재한다. 한개씩 알아보도록 하자.

SimpleControllerHandlerAdapter

Controller 인터페이스를 사용하면 위의 어뎁터를 사용한다.

public class AccountController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ///... 
        return null;
    }
}

위와 같이 Controller 인터페이스를 구현하면 SimpleControllerHandlerAdapter 통해 해당 메서드가 호출 된다.

HttpRequestHandlerAdapter

이것 역시 HttpRequestHandler 인터페이스를 사용하면 해당 어뎁터를 사용한다.

public class AccountController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)  {
        ///...
    }
}

SimpleServletHandlerAdapter

클래스명 그대로 Servlet을 구현하면 된다. 아마도 기본적으로는 설정이 되어 있지 않다. 만약 사용을 원한다면 해당 어뎁터를 빈으로 등록해야 한다.

public class AccountController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}

RequestMappingHandlerAdapter

이것은 아마도 우리가 가장많이 쓰는 어뎁터이다. 실제로는 RequestMappingHandlerMapping 과 대응되는 클래스이다. 만약 RequestMappingHandlerMapping 매핑 전략을 사용하고 싶다면 해당 어뎁터를 이용해야 한다.

해당 어뎁터에선 어떤 메서드를 호출해야 할지 결정해야하므로 이 클래스엔 여러 정보들이 담겨서 있다. 왜냐하면 위의처럼 특정한 인터페이스를 구현한게 아니라 리플렉션을 이용해서 메서드를 호출해야 하므로 해당 파라미터 타입과 리턴타입들의 정보를 알아야 하기 때문에 해당 정보들을 파싱해줄 HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler 인터페이스가 존재한다. 이외에도 더 많은 정보들을 가지고 있지만 여기선 중요한게 아니므로 생략 하겠다.

@Controller
@RequestMapping("/accounts")
public class AccountController {


    @GetMapping("/{id}")
    public String hello(@PathVariable Long id) {

       //...
    }
}

위의 클래스는 RequestMappingHandlerAdapter 통해 hello(Long) 이란 메서드가 호출된다.

HandlerMapping 와 HandlerAdapter 의 연관관계

위에서도 언급을 했지만 RequestMappingHandlerAdapterRequestMappingHandlerMapping은 연관관계가 있다. RequestMappingHandlerMapping 전략을 사용하고 싶다면 꼭 RequestMappingHandlerAdapter 어뎁터를 이용해야 한다.

하지만 다른 전략들은 연관이 있어도 되며 없어도 된다.

예를들어 다음과 같다.

@Bean("/accounts")
public AccountController accountController() {
    return new AccountController();
}

위와 같이 Mapping 전략은 BeanNameUrlHandlerMapping을 사용하면서 여러 어뎁터를 사용할 수 있다.

public class AccountController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //...   
        return ..;
    }
}


public class AccountController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)  {

        //...
    }
}


public class AccountController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

BeanNameUrlHandlerMapping 전략을 사용했지만 어뎁터는 아무 어뎁터나 사용할 수 있다. SimpleUrlHandlerMapping 전략도 마찬가지로 가능하다.

Spring에선 다양한 Mapping 전략과 다양한 Adapter를 제공함으로써 좀 더 유연하게 개발 할 수 있게 한다.

특별한 경우가 아니라면 직접적으로는 다른 전략들은 사용하지 않을 듯 싶다.
사실 간접적으로(?)는 우린 가끔 사용하고 있다.

class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

    }
}

ResourceHandlerRegistry, ViewControllerRegistry 는 내부적으로 SimpleUrlHandlerMapping 전략을 사용하고 있다.

오늘은 이렇게 HandlerMapping 와 HandlerAdapter 에 대해서 살펴봤다. spring web(서블릿)과 관련해서만 살펴봤지만 관심있으면 webflux도 살펴보는 것도 나쁘진 않아 보인다. 비슷한 아키텍처로 구현되어 있긴 하겠지만…

참고로 spring 5.2 부터는 RouterFunctionMapping 클래스가 추가 되었다. 리액티브에만 있던 매핑 전략이 서블릿에도 추가가 되었다.(아마 코틀린을 위한 거겠지?) 아직 릴리즈는 되지 않았지만 한번 살펴보는 것도 좋겠다.
관심있으면 여기를 스리슬쩍 참고하자.