sirve servlet requestmapping que para mvc modelandview ejemplo spring spring-mvc spring-boot

servlet - spring jsp



Interfaz de controlador anotada Spring MVC con @PathVariable (3)

¿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<String, Long> 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<String, Long> 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 declaración 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 primavera.

Extendí esta biblioteca:

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

Identificar un servicio de descanso de primavera

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

Configuro mi fábrica:

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

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

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é

<bean class="....WebConfig" />