본문 바로가기

[SpringBoot] @RequestMapping의 상속 처리

by Ohmry 2022. 10. 25.
이 글은 Spring Boot 2.7.4 버전을 기준으로 작성되었습니다.

 

SpringBoot에서 @RequestMapping으로 API의 경로를 지정할 때, 상위 컨트롤러에 정의된 @RequestMapping을 오버라이드(Override)하지 않고, 상속하여 이어주는 방법은 아래와 같습니다. 예를 들어, API 컨트롤러에 대한 공통영역을 정의한 BaseApiController 클래스가 존재하고, 하위에 각 업무 또는 서비스별로 컨트롤러를 생성한다고 가정하였을 때, 아래와 같은 코드를 갖게 됩니다.

// BaseApiController.java
@RestController
@RequestMapping("/v1")
public abstract class BaseApiController {
  ...
}

// ServiceApiController.java
@RestController
@RequestMapping("/service")
public class ServiceApiController extends BaseApiController {
  @GetMapping("/request")
  public ResponseEntity request() {
    return ResponseEntity.ok().build();
  }
}

위와 같은 형태로 서버를 구동할 경우, ServiceApiController에 존재하는 request 함수에 접근하기 위해서는 /service/request로 호출해야 접근할 수 있습니다. 부모 컨트롤러에 선언된 @RequestMapping 정보는 자식 컨트롤러에 선언된 @RequestMapping에 의해 오버라이드(Override) 되므로 /v1의 값은 사라지게 됩니다. 이 때, 부모 컨트롤러에 선언한 @RequestMapping의 값을 상속받아서 /v1/service/request 의 경로를 생성하기 위해서는 아래와 같이 설정이 필요합니다.

 

RequestMappingHandlerMapping 생성

RequestMappingHandlerMapping은 RequestMappingHandler를 매핑해주는 클래스입니다. RequestMappingHandler는  각 함수에 선언된 @RequestMapping 값을 가져와서 DispatcherServlet이 어떤 URL로 요청이 왔을 때, 특정 컨트롤러로 보낼 수 있도록 매핑정보를 만들어주는 역할을 담당합니다. 따라서 매핑정보를 만드는 과정에서 오버라이드(Override) 되는 부분을 수정해야하므로 기본적으로 사용되는 RequestMappingHandlerMapping를 상속받아서 새로운 클래스를 아래와 같이 생성합니다.

// CustomRequestMappingHandler.java
public class CustomRequestMappingHandler extends RequestMappingHandlerMapping {
  @Override
  protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);

    // 요청 URL 정보가 없을 경우 null을 반환합니다.
    if (requestMappingInfo == null) {
      return null;
    }

    List<String> superclassUrlPatterns = new ArrayList<>();
    Class<?> superclass = handlerType.getSuperclass();
    // Method의 상위 클래스를 모두 검색하면서
    // 클래스에 @RequestMapping 값이 존재할 경우 Pattern을 추가합니다.
    for (; superclass != Object.class; superclass = superclass.getSuperclass()) {
      if (superclass.isAnnotationPresent(RequestMapping.class)) {
        superclassUrlPatterns.add(0, superclass.getAnnotation(RequestMapping.class).value()[0]);
      }
    }

    // 상위 클래스에 @RequestMapping이 존재했다면
    // Pattern을 기준으로 RequestMappingInfo를 생성하고, 기존 RequestMapping 정보와 결합합니다.
    if (!superclassUrlPatterns.isEmpty()) {
      RequestMappingInfo superclassRequestMappingInfo = RequestMappingInfo.paths(String.join("", superclassUrlPatterns)).build();
      return superclassRequestMappingInfo.combine(requestMappingInfo);
    } else {
    // 상위 클래스에 @RequestMapping이 없는 경우
    // 기존 RequestMappingInfo를 그대로 반환합니다.
      return requestMappingInfo;
    }
  }
}

이렇게 생성한 RequestMappingHandler 정보를 Dispatcher Servlet이 초기화될 때, 사용될 수 있도록 DelegatingWebMvcConfiguration을 상속받는 클래스를 생성합니다.

// RequestMappingConfiguration.java
@Configuration
public class RequestMappingConfiguration extends DelegatingWebMvcConfiguration {
  @Override
  protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
    return new CustomRequestMappingHandler();
  }
}

위와 같이 2개의 클래스를 생성하고, 서버를 구동한 뒤 /v1/service/request로 접속을 한다면 응답을 받을 수 있습니다.

'SpringBoot' 카테고리의 다른 글

spring-boot-devtools 설정  (0) 2023.03.07

댓글