[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 어노테이션을 사용하여 **에 해당하는 값을 문자열배열로 바인딩하려고 합니다.

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


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

/**
 * 패턴이 "/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)를 반환합니다.
자세한 것은 스프링 소스코드 주석에 있는 샘플을 참고해주세요.

/**
* 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에 등록합니다.

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

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


감사합니다.

댓글

Popular Posts

AI 시대, SEO가 아닌 GEO에 포커싱해야 하는 이유

AI 메모리 HBM 외에 HBF도 주목

네이버 쇼핑 잘 나가네요, 구팡이 절대 강자인줄~