Spring MVC con annotazioni Controller di Interfaccia con @PathVariable

C’è alcun motivo per non mappa Controller interfacce?

In tutti gli esempi e domande vedo circostante controller, tutti sono classi concrete. C’è un motivo per questo? Vorrei separare la richiesta di mapping di attuazione. Mi ha colpito un muro, però quando ho cercato di ottenere una @PathVariable come parametro nella mia classe di calcestruzzo.

Mio Controller interfaccia simile a questo:

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

}

E l’attuazione di classe:

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

}

Il getGoals() metodo 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 posso aggiungere la @PathVariable annotazione per la classe di calcestruzzo con tutto funziona come previsto, ma perché devo ri-dichiarare il presente nel calcestruzzo di classe? Non dovrebbe essere gestita da ciò che è il @Controller annotazione?

  • Sembra che io riuscivo a capire annotazione eredità, io dopo la mia spiegazione dopo che ho attendere per le mie 8 ore di timeout per scadere
InformationsquelleAutor will-ob | 2011-11-03

 

4 Replies
  1. 23

    A quanto pare, quando un modello di richiesta è mappato a un metodo tramite il @RequestMapping annotazione, viene mappato per la concreta implementazione del metodo. In modo che una richiesta che corrisponde alla dichiarazione di richiamare GoalServiceImpl.removeGoal() direttamente, piuttosto che il metodo che è stato originariamente dichiarato il @RequestMapping ie GoalService.removeGoal().

    Dato un’annotazione su un’interfaccia, interfaccia metodo, o parametri del metodo di interfaccia non riporto per la realizzazione è alcun modo per Spring MVC riconoscere questo come un @PathVariable a meno che l’attuazione di classe, che si dichiara in modo esplicito. Senza di essa, qualsiasi AOP consigli che gli obiettivi di @PathVariable parametri non verrà eseguito.

    • Per essere chiari, non c’è motivo di Spring MVC non avrebbe potuto fare questo. Funziona bene in Jersey. Ma hanno scelto di non per qualsiasi motivo.
    • pieno supporto per le annotazioni sulle interfacce è stato aggiunto di recente – si prega di vedere la mia risposta qui sotto e vota
  2. 8

    Funziona nella versione più recente di Primavera.

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

    Implementare l’interfaccia del Controller

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

    Può essere utilizzato come:
    Client Rest

    • Hai provato questa con annotati parametri?
    • Das quale versione stai usando ?
    • primavera 4.3.* opere. Ma non c’è params in questo esempio. spring-mvc gestore adattatori non sono a conoscenza di qualsiasi annotata params nel metodo di interfaccia. E c’è qualche soluzione alternativa: metodo di Interfaccia con spring-mvc, le annotazioni possono avere implementazione di default. Questa implementazione delegati chiamata ad un altro abstract metodo, che viene implementato dalla classe con @RestController annotazione. In questo scenario la primavera si lega gestore di richieste http al metodo di interfaccia con l’implementazione di default. Vedi esempio semplice
    • Il semplice esempio si forniscono sembra contenere un errore. Il controller di ignorare l’impostazione predefinita metodo di interfaccia sayHello invece di sayHelloImpl. Non penso che questo codice viene compilato. Ma ho provato quello che vuoi dire e la soluzione funziona bene 😉
    • Sì, grazie! Gist fisso — classe sostituzioni metodo astratto.
    • il tuo commento è molto completa risposta. Si potrebbe pubblicare una risposta invece di un commento, come è molto importante e rappresenta un esempio di soluzione per il caso della Primavera precedente a 4.3 😉
    • pieno supporto per le annotazioni sulle interfacce è stato aggiunto di recente – si prega di vedere la mia risposta qui sotto e vota

  3. 3

    La caratteristica di definizione di tutte le associazioni interfaccia effettivamente implementare recentemente in Primavera 5.1.5.

    Si prega di vedere questo problema: https://github.com/spring-projects/spring-framework/issues/15682 – e ‘ stata una lotta 🙂

    Ora si può effettivamente fare:

    @RequestMapping("/random")
    public interface RandomDataController {
    
        @RequestMapping(value = "/{type}", method = RequestMethod.GET)
        @ResponseBody
        RandomData getRandomData(
                @PathVariable(value = "type") RandomDataType type, @RequestParam(value = "size", required = false, defaultValue = "10") int size);
    }
    @Controller
    public class RandomDataImpl implements RandomDataController {
    
        @Autowired
        private RandomGenerator randomGenerator;
    
        @Override
        public RandomData getPathParamRandomData(RandomDataType type, int size) {
            return randomGenerator.generateRandomData(type, size);
        }
    }

    È anche possibile utilizzare questa libreria: https://github.com/ggeorgovassilis/spring-rest-invoker

    Per ottenere un client-proxy basato su tale interfaccia, allo stesso modo di come RestEasys framework client opere JAX-RS terra.

  4. 0

    ho risolto questo problema.

    SUL LATO CLIENT:

    Sto usando questa libreria https://github.com/ggeorgovassilis/spring-rest-invoker/. Questa libreria generare un proxy da interfaccia per richiamare la primavera di riposo servizio.

    Ho esteso questa libreria:

    Ho creato un annotazioni e di una fabbrica di classe client:

    Identificare una Molla di Riposo Servizio

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

    Questa classe genera un client resto da 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<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);
        }
    }

    Posso configurare la mia fabbrica:

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

    SUL RESTO DEL SERVIZIO DI FIRMA

    questo è un esempio di interfaccia:

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

    SUL RESTO DELL’IMPLEMENTAZIONE DEL SERVIZIO

    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 le annotazioni su parametri di creare un custom RequestMappingHandlerMapping che guarda tutte le interfacce annotata 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;
                }
    
            }
    
        }
    
    }

    Ho creato una classe 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 ho configurato è

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

Lascia un commento