복붙노트

[SPRING] @PathVariable을 사용한 스프링 MVC 주석 컨트롤러 인터페이스

SPRING

@PathVariable을 사용한 스프링 MVC 주석 컨트롤러 인터페이스

컨트롤러를 인터페이스로 매핑하지 않는 이유가 있습니까?

주변 컨트롤러를 보는 모든 예제와 질문에서 모두 구체적인 클래스입니다. 이것에 대한 이유가 있습니까? 요청 매핑을 구현과 분리하고 싶습니다. 내 구체적인 클래스에서 매개 변수로 @PathVariable을 가져 오려고했지만 벽을 때렸다.

내 컨트롤러 인터페이스는 다음과 같습니다.

@Controller
@RequestMapping("/services/goal/")
public interface GoalService {

    @RequestMapping("options/")
    @ResponseBody
    Map<String, Long> getGoals();

    @RequestMapping(value = "{id}/", method = RequestMethod.DELETE)
    @ResponseBody
    void removeGoal(@PathVariable String id);

}

그리고 구현 클래스 :

@Component
public class GoalServiceImpl implements GoalService {

    /* init code */

    public Map<String, Long> getGoals() {
        /* method code */
        return map;
    }

    public void removeGoal(String id) {
        Goal goal = goalDao.findByPrimaryKey(Long.parseLong(id));
        goalDao.remove(goal);
    }

}

getGoals () 메서드는 훌륭하게 작동합니다. removeGoal (String id) 예외를 throw합니다.

ExceptionHandlerExceptionResolver - Resolving exception from handler [public void
    todo.webapp.controllers.services.GoalServiceImpl.removeGoal(java.lang.String)]: 
    org.springframework.web.bind.MissingServletRequestParameterException: Required 
    String parameter 'id' is not present

@PathVariable 주석을 콘크리트 클래스에 추가하면 모든 것이 예상대로 작동하지만 왜 이것을 구체적인 클래스에서 다시 선언해야합니까? @Controller 어노테이션이있는 것은 무엇이든 처리해야하지 않습니까?

해결법

  1. ==============================

    1.분명히, 요청 패턴이 @RequestMapping 주석을 통해 메소드에 매핑 될 때, 그것은 구체적인 메소드 구현으로 매핑됩니다. 따라서 선언과 일치하는 요청은 원래 @RequestMapping, 즉 GoalService.removeGoal ()을 선언 한 메서드가 아니라 GoalServiceImpl.removeGoal ()을 직접 호출합니다.

    분명히, 요청 패턴이 @RequestMapping 주석을 통해 메소드에 매핑 될 때, 그것은 구체적인 메소드 구현으로 매핑됩니다. 따라서 선언과 일치하는 요청은 원래 @RequestMapping, 즉 GoalService.removeGoal ()을 선언 한 메서드가 아니라 GoalServiceImpl.removeGoal ()을 직접 호출합니다.

    인터페이스, 인터페이스 메소드 또는 인터페이스 메소드 매개 변수의 주석은 구현으로 이어지지 않으므로 구현 클래스가 명시 적으로 선언하지 않는 한 Spring MVC가 이것을 @PathVariable로 인식 할 수 없습니다. 그것없이 @PathVariable 매개 변수를 대상으로하는 AOP 조언은 실행되지 않습니다.

  2. ==============================

    2.최신 버전의 Spring에서 작동합니다.

    최신 버전의 Spring에서 작동합니다.

    import org.springframework.web.bind.annotation.RequestMapping;
    public interface TestApi {
        @RequestMapping("/test")
        public String test();
    }
    

    컨트롤러에 인터페이스 구현하기

    @RestController
    @Slf4j
    public class TestApiController implements TestApi {
    
        @Override
        public String test() {
            log.info("In Test");
            return "Value";
        }
    
    }
    

    그것은 다음과 같이 사용될 수 있습니다 : 휴식 클라이언트

  3. ==============================

    3.나는이 문제를 해결했다.

    나는이 문제를 해결했다.

    고객 측 :

    이 라이브러리 https://github.com/ggeorgovassilis/spring-rest-invoker/를 사용하고 있습니다. 이 라이브러리는 스프링 휴식 서비스를 호출하기 위해 인터페이스에서 프록시를 생성합니다.

    이 라이브러리를 확장했습니다.

    주석과 공장 클라이언트 클래스를 만들었습니다.

    스프링 레스트 서비스 확인

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface SpringRestService {
        String baseUri();
    }
    

    이 클래스는 인터페이스에서 클라이언트 레스트를 생성합니다.

    public class RestFactory implements BeanFactoryPostProcessor,EmbeddedValueResolverAware  {
    
        StringValueResolver resolver;
    
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            this.resolver = resolver;
        }
        private String basePackage = "com";
    
        public void setBasePackage(String basePackage) {
            this.basePackage = basePackage;
        }
    
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            createBeanProxy(beanFactory,SpringRestService.class);
            createBeanProxy(beanFactory,JaxrsRestService.class);
        }
    
        private void createBeanProxy(ConfigurableListableBeanFactory beanFactory,Class<? extends Annotation> annotation) {
            List<Class<Object>> classes;
            try {
                classes = AnnotationUtils.findAnnotatedClasses(basePackage, annotation);
            } catch (Exception e) {
                throw new BeanInstantiationException(annotation, e.getMessage(), e);
            }
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            for (Class<Object> classType : classes) {
                Annotation typeService = classType.getAnnotation(annotation);   
                GenericBeanDefinition beanDef = new GenericBeanDefinition();
                beanDef.setBeanClass(getQueryServiceFactory(classType, typeService));
                ConstructorArgumentValues cav = new ConstructorArgumentValues();
                cav.addIndexedArgumentValue(0, classType);
                cav.addIndexedArgumentValue(1, baseUri(classType,typeService));
                beanDef.setConstructorArgumentValues(cav);
                registry.registerBeanDefinition(classType.getName() + "Proxy", beanDef);
            }
        }
    
        private String baseUri(Class<Object> c,Annotation typeService){
            String baseUri = null;
            if(typeService instanceof SpringRestService){
                baseUri = ((SpringRestService)typeService).baseUri();  
            }else if(typeService instanceof JaxrsRestService){
                baseUri = ((JaxrsRestService)typeService).baseUri();
            }
            if(baseUri!=null && !baseUri.isEmpty()){
                return baseUri = resolver.resolveStringValue(baseUri);
            }else{
                throw new IllegalStateException("Impossibile individuare una baseUri per l'interface :"+c);
            }
        }
    
        private static Class<? extends FactoryBean<?>> getQueryServiceFactory(Class<Object> c,Annotation typeService){
            if(typeService instanceof SpringRestService){
                return it.eng.rete2i.springjsonmapper.spring.SpringRestInvokerProxyFactoryBean.class;  
            }else if(typeService instanceof JaxrsRestService){
                return it.eng.rete2i.springjsonmapper.jaxrs.JaxRsInvokerProxyFactoryBean.class;
            }
            throw new IllegalStateException("Impossibile individuare una classe per l'interface :"+c);
        }
    }
    

    내 공장 구성 :

    <bean class="it.eng.rete2i.springjsonmapper.factory.RestFactory">
        <property name="basePackage" value="it.giancarlo.rest.services" />
    </bean>
    

    휴식 서비스 서명

    다음은 예제 인터페이스입니다.

    package it.giancarlo.rest.services.spring;
    
    import ...
    
    @SpringRestService(baseUri="${bookservice.url}")
    public interface BookService{
    
        @Override
        @RequestMapping("/volumes")
        QueryResult findBooksByTitle(@RequestParam("q") String q);
    
        @Override
        @RequestMapping("/volumes/{id}")
        Item findBookById(@PathVariable("id") String id);
    
    }
    

    휴식 서비스 구현

    서비스 구현

    @RestController
    @RequestMapping("bookService")
    public class BookServiceImpl implements BookService {
        @Override
        public QueryResult findBooksByTitle(String q) {
            // TODO Auto-generated method stub
            return null;
        }
        @Override
        public Item findBookById(String id) {
            // TODO Auto-generated method stub
            return null;
        }
    }
    

    매개 변수에 대한 주석을 해결하기 위해 @SpringRestService로 주석 된 모든 인터페이스를 찾는 사용자 정의 RequestMappingHandlerMapping을 만듭니다.

    public class RestServiceRequestMappingHandlerMapping extends RequestMappingHandlerMapping{
    
    
        public HandlerMethod testCreateHandlerMethod(Object handler, Method method){
            return createHandlerMethod(handler, method);
        }
    
        @Override
        protected HandlerMethod createHandlerMethod(Object handler, Method method) {
            HandlerMethod handlerMethod;
            if (handler instanceof String) {
                String beanName = (String) handler;
                handlerMethod = new RestServiceHandlerMethod(beanName,getApplicationContext().getAutowireCapableBeanFactory(), method);
            }
            else {
                handlerMethod = new RestServiceHandlerMethod(handler, method);
            }
            return handlerMethod;
        }
    
    
        public static class RestServiceHandlerMethod extends HandlerMethod{
    
            private Method interfaceMethod;
    
    
            public RestServiceHandlerMethod(Object bean, Method method) {
                super(bean,method);
                changeType();
            }
    
            public RestServiceHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
                super(bean,methodName,parameterTypes);
                changeType();
            }
    
            public RestServiceHandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
                super(beanName,beanFactory,method);
                changeType();
            }
    
    
            private void changeType(){
                for(Class<?> clazz : getMethod().getDeclaringClass().getInterfaces()){
                    if(clazz.isAnnotationPresent(SpringRestService.class)){
                        try{
                            interfaceMethod = clazz.getMethod(getMethod().getName(), getMethod().getParameterTypes());
                            break;      
                        }catch(NoSuchMethodException e){
    
                        }
                    }
                }
                MethodParameter[] params = super.getMethodParameters();
                for(int i=0;i<params.length;i++){
                    params[i] = new RestServiceMethodParameter(params[i]);
                }
            }
    
    
    
    
            private class RestServiceMethodParameter extends MethodParameter{
    
                private volatile Annotation[] parameterAnnotations;
    
                public RestServiceMethodParameter(MethodParameter methodParameter){
                    super(methodParameter);
                }
    
    
                @Override
                public Annotation[] getParameterAnnotations() {
                    if (this.parameterAnnotations == null){
                            if(RestServiceHandlerMethod.this.interfaceMethod!=null) {
                                Annotation[][] annotationArray = RestServiceHandlerMethod.this.interfaceMethod.getParameterAnnotations();
                                if (this.getParameterIndex() >= 0 && this.getParameterIndex() < annotationArray.length) {
                                    this.parameterAnnotations = annotationArray[this.getParameterIndex()];
                                }
                                else {
                                    this.parameterAnnotations = new Annotation[0];
                                }
                            }else{
                                this.parameterAnnotations = super.getParameterAnnotations();
                            }
                    }
                    return this.parameterAnnotations;
                }
    
            }
    
        }
    
    }
    

    구성 클래스를 만들었습니다.

    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport{
    
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            RestServiceRequestMappingHandlerMapping handlerMapping = new RestServiceRequestMappingHandlerMapping();
            handlerMapping.setOrder(0);
            handlerMapping.setInterceptors(getInterceptors());
            handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
    
            PathMatchConfigurer configurer = getPathMatchConfigurer();
            if (configurer.isUseSuffixPatternMatch() != null) {
                handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
            }
            if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
                handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
            }
            if (configurer.isUseTrailingSlashMatch() != null) {
                handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
            }
            if (configurer.getPathMatcher() != null) {
                handlerMapping.setPathMatcher(configurer.getPathMatcher());
            }
            if (configurer.getUrlPathHelper() != null) {
                handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper());
            }
            return handlerMapping;
        }
    }
    

    나는 그것을 구성했다.

    <bean class="....WebConfig" />
    
  4. from https://stackoverflow.com/questions/8002514/spring-mvc-annotated-controller-interface-with-pathvariable by cc-by-sa and MIT license