Spring MVC Annotated Controller Interface con @PathVariable

C’è qualche motivo per non mappare i controller come interfacce?

In tutti gli esempi e le domande che vedo sui controllori circostanti, tutti sono classi concrete. C’è una ragione per questo? Vorrei separare i mapping delle richieste dall’implementazione. Ho colpito un muro però quando ho cercato di ottenere un parametro @PathVariable come parametro nella mia class concreta.

L’interfaccia del mio controller appare così:

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

E la class di implementazione:

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

Il metodo getGoals() funziona alla grande; il removeGoal(String id) genera un’eccezione

 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 

Se aggiungo l’annotazione @PathVariable alla class concreta, tutto funziona come previsto, ma perché dovrei ri-dichiarare questo nella class concreta? Non dovrebbe essere gestito da qualunque cosa abbia l’annotazione @Controller ?

Apparentemente, quando un modello di richiesta viene mappato su un metodo tramite l’annotazione @RequestMapping , viene mappato all’implementazione del metodo concreto. Quindi una richiesta che corrisponde alla dichiarazione invocherà direttamente GoalServiceImpl.removeGoal() piuttosto che il metodo che ha originariamente dichiarato @RequestMapping ovvero GoalService.removeGoal() .

Poiché un’annotazione su un’interfaccia, un metodo di interfaccia o un parametro del metodo di interfaccia non viene trasferita all’implementazione, @PathVariable non è in @PathVariable di riconoscere questo come @PathVariable meno che la class di implementazione non lo dichiari esplicitamente. Senza di esso, qualsiasi avviso AOP che indirizzi i parametri @PathVariable non verrà eseguito.

Funziona nella versione più recente di Spring.

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

Implementare l’interfaccia nel controller

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

Può essere usato come: client Rest

ho risolto questo problema

SUL LATO DEL CLIENTE:

Sto utilizzando questa libreria https://github.com/ggeorgovassilis/spring-rest-invoker/ . Questa libreria genera un proxy dall’interfaccia per invocare il servizio di rest a molla.

Ho esteso questa libreria:

Ho creato un’annotazione e una class client factory:

Identificare un servizio di rest primaverile

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

Questa class genera un resto client dalle interfacce

 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> classs; try { classs = AnnotationUtils.findAnnotatedClasses(basePackage, annotation); } catch (Exception e) { throw new BeanInstantiationException(annotation, e.getMessage(), e); } BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (Class classType : classs) { 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("Imansible 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("Imansible individuare una class per l'interface :"+c); } } 

Configuro la mia fabbrica:

    

SULLA FIRMA DEL SERVIZIO DI RIPOSO

questa è un’interfaccia di esempio:

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

SULL’IMPLEMENTAZIONE DEL SERVIZIO DI RESTO

Implementazione del servizio

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

Per risolvere l’annotazione sui parametri, creo un RequestMappingHandlerMapping personalizzato che guarda tutte le interfacce annotate 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; } } } } 

Ho creato una class di configurazione

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

e l'ho configurato