Spring

스프링의 핸들러 매핑과 어댑터

monkeyDugi 2022. 1. 24. 12:10
반응형

스프링은 어댑터 패턴 기반의 프론트 컨트롤러 패턴으로 컨트롤러를 제공하는
핸들러 매핑과 어댑터를 제공한다.

과거에 사용했던 것들을 알아 보자.


Controller 인터페이스

  • @Controller 아님
package org.springframework.web.servlet.mvc;

@FunctionalInterface
public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render. A {@code null} return value is not an error: it indicates that
     * this object completed request processing itself and that there is therefore no
     * ModelAndView to render.
     * @param request current HTTP request
     * @param response current HTTP response
     * @return a ModelAndView to render, or {@code null} if handled directly
     * @throws Exception in case of errors
     */
    @Nullable
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}
package org.springframework.web.servlet.handler;

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

    /**
     * Checks name and aliases of the given bean for URLs, starting with "/".
     */
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

}
package org.springframework.web.servlet.mvc;

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    @SuppressWarnings("deprecation")
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }

}

만약 직접 HandlerAdapter를 만들고 싶다면 HandlerAdapter만 구현해주면 된다.
위의 핸들러 인터페이스를 구현 해보면 아래와 같다.

  • 빈의 이름으로 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 null;
    }
}

컨트롤러가 호출되는 과정

HandlerMapping

  • 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
  • 예) 스프링 빈의 이름으로 핸들러(Controller)를 찾을 수 있는 핸들러 매핑이 필요하다.

HandlerAdapter

  • 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
  • 예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행한다.

스프링 부트가 자동으로 등록하는 핸들러 매핑과 핸들러 어댑터

스프링에는 거의 다 제공이 되기 때문에 2개만 알아보도록 하자.

handlerMapping

앞의 숫자는 우선 순위 이다.
결과적으로는 RequestMappingHandleMapping, RequestMappingHandlerAdapter를 거의 사용한다.

0 = RequestMappingHandleMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandleMppaing : 스프링 빈 이름으로 핸들러를 찾는다.

HandlerAdapter

0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequesthandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandleAdapter : Contoller 인터페이스(애노테이션 x, 과거에 사용) 처리

핸들러 매핑, 핸들러 어댑터를 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.

BeanNameUrlHandleMppaing, SimpleControllerHandleAdapter의 실행 과정을 알아보자.

  1. 핸들러 매핑으로 핸들러 조회
    1. BeanNameUrlHandleMppaingOldContoller를 반환한다.
  2. 핸들러 어댑터 조회
    1. HandlerAdapter의 supports()를 순서대로 호출한다.
    2. SimpleControllerHandleAdapterController 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿(프론트 컨트롤러)이 조회한 SimpleControllerHandleAdapter를 실행하면서
      핸들러 정보도 같이 넘겨준다.
    2. SimpleControllerHandleAdapter는 핸들러인 OldController를 내부에서 실행하고, 그 결과를 반환한다.

OldContoller 핸들러 매핑, 어댑터 정리

  • HandlerMapping = BenaNameUrlHandlerMapping
  • HandlerAdapter = SimpleControllerHandlerAdapter

@RueqestMapping


요즘은 위의 두 방식은 불편하기에 사용하지 않고, 이 방식을 사용한다.
아마 가장 익숙한 모양일 것이다.

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

각 애노테이션을 알아보자.

@Controller

  • 내부에 @Component가 있기 때문에 스프링이 자동으로 빈으로 등록

@RequestMapping

  • 요청 정보를 매핑한다.
  • 해당 URL이 호출되면 이 메서드가 호출 된다. 애노테이션 기반이기 때문에 메서드 명은 상관없다.

ModelAndVeiw

  • 모델과 뷰 정보를 담아서 반환한다.

RequestMappingHandleMapping

  • 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
  • 스프링 빈 중에서 @ReuqestMapping 또는 @Controller가 클래스 레벨에 부터 있으면
    매핑 정보로 인식한다.

즉, 아래 코드도 동작한다.

@Component
@RequestMapping
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

선 조건이 빈으로 등록이 되어야 하는 것이기 때문에 아래 코드도
빈으로 등록만 해준다면 동작한다.

@RequestMapping
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

단, 빈 등록을 수동으로 해주어야 함..

@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServletApplication.class, args);
    }

    @Bean
    SpringMemberFormControllerV1 springMemberFormControllerV1() {
        return new SpringMemberFormControllerV1();
    }
}
반응형