Spring MVC anotada controlador de interface com @PathVariable

votos
28

Existe alguma razão para não mapear controladores como interfaces?

Em todos os exemplos e perguntas vejo controladores circundantes, todos são classes concretas. Existe uma razão para isso? Eu gostaria de separar os mapeamentos solicitação da implementação. Eu bati em uma parede que quando eu tentei obter um @PathVariablecomo um parâmetro na minha classe concreta.

Minha interface do controlador se parece com isso:

@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 a classe de execução:

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

}

O getGoals()método funciona muito bem; a removeGoal(String id)lança uma excepção

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 eu adicionar a @PathVariableanotação para a classe concreta tudo funciona como esperado, mas por que eu deveria ter de voltar a declarar isso na classe concreta? Não deveria ser tratado por tudo o que tem a @Controlleranotação?

Publicado 03/11/2011 em 23:07
fonte usuário
Em outras línguas...                            


4 respostas

votos
21

Aparentemente, quando um padrão pedido é mapeado para um método através da @RequestMappinganotação, ele é mapeado para a implementação do método concreto. Assim, um pedido que corresponde a declaração irá invocar GoalServiceImpl.removeGoal()directamente em vez do método que originalmente declarado o @RequestMappingie GoalService.removeGoal().

Desde uma anotação em uma interface, método de interface, ou parâmetro método de interface não transitar para a implementação não há nenhuma maneira para Spring MVC para reconhecer isso como um @PathVariablea menos que a classe implementar declara explicitamente. Sem ele, qualquer conselho AOP que tem como alvo @PathVariableos parâmetros não serão executadas.

Respondeu 04/11/2011 em 07:31
fonte usuário

votos
7

Ele funciona na versão mais recente do Primavera.

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

Implementar a interface do controlador

@RestController
@Slf4j
public class TestApiController implements TestApi {

    @Override
    public String test() {
        log.info("In Test");
        return "Value";
    }

}

Ele pode ser usado como: cliente REST

Respondeu 21/05/2016 em 06:14
fonte usuário

votos
0

i resolvido este problema.

No lado do cliente:

Eu estou usando esta biblioteca https://github.com/ggeorgovassilis/spring-rest-invoker/ . Esta biblioteca gerar um proxy de interface para invocar o serviço resto primavera.

I estendeu esta biblioteca:

Criei um anotações e uma classe de cliente da fábrica:

Identificar um Serviço Primavera Resto

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

Esta classe gera um descanso cliente das 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);
    }
}

Eu configurar minha fábrica:

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

NO DESCANSO SERVIÇO DE ASSINATURA

esta é uma interface exemplo:

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

}

NO DESCANSO SERVIÇO DE IMPLEMENTAÇÃO

implementação do serviço

@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 anotação em parâmetros que criam uma RequestMappingHandlerMapping personalizado que parece todas as interfaces anotados com @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;
            }

        }

    }

}

Eu criei uma classe de configuração

@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 eu a configurou

<bean class="....WebConfig" />
Respondeu 11/10/2016 em 12:28
fonte usuário

votos
0

O recurso de definir todas as ligações na interface de realmente tenho implementar recentemente na Primavera 5.1.5.

Por favor, veja esta questão: https://github.com/spring-projects/spring-framework/issues/15682 - foi uma luta :)

Agora você pode realmente fazer:

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

Você ainda pode usar esta biblioteca: https://github.com/ggeorgovassilis/spring-rest-invoker

Para obter um cliente-proxy com base nessa interface, da mesma forma como estrutura de cliente RestEasys trabalha na terra JAX-RS.

Respondeu 13/03/2019 em 22:08
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more