[Spring] @RequestMapping의 wildcard 패턴 **의 값을 배열로 얻기

스프링 프레임워크를 이용하여 컨트롤러에 패턴을 등록하면 다양한 URI로 매핑시킬 수 있다.

이때 와일드카드(wildcard)를 자주 사용하게 되는데 기본적으로 ?, *, ** 가 있다.

아래는 스프링 문서 설명이다.


※ 출처: Spring Web MVC - Web on Servlet Stack (https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html)




또한, {XXXXX}과 @PathVariable 어노테이션을 이용하여 URI의 특정부분을 변수에 할당할 수도 있다.




그리고 정규표현식(regular expression)도 지원한다.




가끔 매핑 패턴을 /path1/path2/** 처럼 여러개의 경로를 지원할 필요가 있는데, 단순하게 매핑뿐만 아니라 실제 ** 에 해당하는 경로를 얻어야할 때가 있다.

** 에 해당하는 값을 @PathVariable 처럼 문자열(String) 또는 문자열배열(String[])로 쉽게 바인딩하는 것을 스프링에서 지원하면 좋겠지만, 아무리 찾아봐도 없다.

그래서 이것을 구현하기 위한 방법을 설명하려고 한다.

혹시라도 스프링 기본기능으로 바인딩하는 방법을 아시는 분은 댓글로 남겨주세요.



아래와 같이 컨트롤러에 @WildcardPathVariable 어노테이션을 사용하여 **에 해당하는 값을 문자열배열로 바인딩하려고 한다.


TestController.java

@Controller
@RequestMapping(value = "/test")
public class TestController {
@GetMapping("/path1/path2/**")
public String[] getPaths(@WildcardPathVariable String[] paths) {
return paths;
}
}



아래는 @WildcardPathVariable 어노테이션 구현체이다.


WildcardPathVariable.java

/**
* 패턴이 "/path1/path2/**"이고 경로가 "/path1/path2/path3/path4"일 때, "path3/path4"를 얻기위한 어노테이션
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface WildcardPathVariable {
}



아래처럼 @WildcardPathVariable이 작동하도록 resolver를 작성한다.

핵심은 스프링에서 Request 객체에 attribute로 설정해주는 값 중, HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE 과 HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE 을 얻는것과, AntPathMatcher를 사용하는 것이다.

특히 AntPathMatcher의 extractPathWithinPattern() 메서드가 중요하다.

이 메서드를 간단하게 설명하면, 입력된 패턴(pattern) 중 wildcard 위치에 해당하는 경로(path)를 반환한다.

자세한 것은 스프링 소스코드 주석에 있는 샘플을 참고하기 바란다.


WildcardPathVariableHandlerMethodArgumentResolver.java

/**
* WildcardPathVariable 어노테이션을 처리하는 resolver
*/
public class WildcardPathVariableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
/**
* AntPathMatcher
*/
private AntPathMatcher antPathMatcher = new AntPathMatcher();

@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(WildcardPathVariable.class);
}

@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) nativeWebRequest.getNativeRequest();

// /path1/path2/**
String pattern = (String) httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);

// /path1/path2/path3/path4
String path = (String) httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);

// 와일드카드 이후로 얻기
String wildcardPath = antPathMatcher.extractPathWithinPattern(pattern, path);

// "/"으로 분리
String[] splitPaths = StringUtils.split(wildcardPath, AntPathMatcher.DEFAULT_PATH_SEPARATOR);

return splitPaths;
}
}



Resolver를 사용하기 위하여 빈으로 정의하고, 스프링 argumentResolver에 등록한다.


WebConfig.java

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(wildcardPathVariableHandlerMethodArgumentResolver());
}

@Bean
public WildcardPathVariableHandlerMethodArgumentResolver wildcardPathVariableHandlerMethodArgumentResolver() {
return new WildcardPathVariableHandlerMethodArgumentResolver();
}
}



댓글(0)

Designed by JB FACTORY