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 extends Annotation> 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
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