Interfaz de controlador anotada Spring MVC con @PathVariable

¿Hay alguna razón para no asignar controladores como interfaces?

En todos los ejemplos y preguntas veo los controladores de los alrededores, todos son clases concretas. ¿Hay alguna razón para esto? Me gustaría separar las asignaciones de solicitudes de la implementación. Sin embargo, @PathVariable contra una pared cuando traté de obtener @PathVariable como parámetro en mi clase concreta.

La interfaz de mi controlador se ve así:

 @Controller @RequestMapping("/services/goal/") public interface GoalService { @RequestMapping("options/") @ResponseBody Map getGoals(); @RequestMapping(value = "{id}/", method = RequestMethod.DELETE) @ResponseBody void removeGoal(@PathVariable String id); } 

Y la clase de implementación:

 @Component public class GoalServiceImpl implements GoalService { /* init code */ public Map getGoals() { /* method code */ return map; } public void removeGoal(String id) { Goal goal = goalDao.findByPrimaryKey(Long.parseLong(id)); goalDao.remove(goal); } } 

El método getGoals() funciona de maravilla; el removeGoal(String id) arroja una excepción

 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 

Si agrego la anotación @PathVariable a la clase concreta todo funciona como se esperaba, pero ¿por qué tendré que volver a declarar esto en la clase concreta? ¿No debería ser manejado por lo que tenga la anotación @Controller ?

Aparentemente, cuando un patrón de solicitud se asigna a un método a través de la anotación @RequestMapping , se asigna a la implementación del método concreto. Por lo tanto, una solicitud que coincida con la statement invocará directamente a GoalServiceImpl.removeGoal() lugar del método que originalmente declaró @RequestMapping ie GoalService.removeGoal() .

Como una anotación en una interfaz, método de interfaz o parámetro de método de interfaz no se @PathVariable a la implementación, Spring MVC no puede reconocer esto como @PathVariable menos que la clase implementadora lo declare explícitamente. Sin él, ningún consejo de AOP que se @PathVariable parámetros de @PathVariable no se ejecutará.

Funciona en una versión más nueva de Spring.

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

Implementar la interfaz en el controlador

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

Se puede usar como: Cliente de reposo

Resolví este problema.

EN EL LADO DEL CLIENTE:

Estoy usando esta biblioteca https://github.com/ggeorgovassilis/spring-rest-invoker/ . Esta biblioteca genera un proxy de la interfaz para invocar el servicio de descanso de spring.

Extendí esta biblioteca:

Creé una anotación y una clase de cliente de fábrica:

Identificar un servicio de descanso de spring

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

Esta clase genera un descanso del cliente de las interfaces

 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 annotation) { List> classes; try { classes = AnnotationUtils.findAnnotatedClasses(basePackage, annotation); } catch (Exception e) { throw new BeanInstantiationException(annotation, e.getMessage(), e); } BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (Class 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 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> getQueryServiceFactory(Class 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); } } 

Configuro mi fábrica:

    

FIRMA DEL SERVICIO DE RESTO

esta es una interfaz de ejemplo:

 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); } 

EN LA IMPLEMENTACIÓN DEL SERVICIO DE RESTO

Implementación del servicio

 @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; } } 

Para resolver la anotación en los parámetros, creo un RequestMappingHandlerMapping personalizado que busca todas las interfaces anotadas con @SpringRestService

 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= 0 && this.getParameterIndex() < annotationArray.length) { this.parameterAnnotations = annotationArray[this.getParameterIndex()]; } else { this.parameterAnnotations = new Annotation[0]; } }else{ this.parameterAnnotations = super.getParameterAnnotations(); } } return this.parameterAnnotations; } } } } 

Creé una clase de configuración

 @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; } } 

y lo configuré