【问题标题】:Spring MVC Annotated Controller Interface with @PathVariable带有 @PathVariable 的 Spring MVC 带注释的控制器接口
【发布时间】:2011-12-21 14:20:29
【问题描述】:

有什么理由不将控制器映射为接口?

在我看到的围绕控制器的所有示例和问题中,都是具体的类。是否有一个原因?我想将请求映射与实现分开。不过,当我试图在我的具体类中获取 @PathVariable 作为参数时,我碰壁了。

我的控制器界面如下所示:

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

}

以及实现类:

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

}

getGoals() 方法效果很好; removeGoal(String id) 抛出异常

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

如果我将@PathVariable 注释添加到具体类中,一切都会按预期工作,但是为什么我必须在具体类中重新声明呢?不应该由有@Controller注解的东西来处理吗?

【问题讨论】:

  • 看来我只是不明白注释继承,等我的 8 小时超时到期后我会发布我的解释

标签: spring spring-mvc spring-boot


【解决方案1】:

显然,当请求模式通过@RequestMapping 注解映射到方法时,它会映射到具体的方法实现。因此,与声明匹配的请求将直接调用GoalServiceImpl.removeGoal(),而不是最初声明@RequestMapping 的方法,即GoalService.removeGoal()

由于接口、接口方法或接口方法参数上的注释不会延续到实现中,因此 Spring MVC 无法将其识别为@PathVariable,除非实现类明确声明它。没有它,任何以@PathVariable 参数为目标的 AOP 建议都不会被执行。

【讨论】:

  • 明确地说,Spring MVC 没有理由不能做到这一点。它在泽西岛工作得很好。但无论出于何种原因,他们都选择不这样做。
  • 最近添加了对接口注释的全面支持 - 请在下面查看我的回答并点赞
【解决方案2】:

在接口上定义所有绑定的特性实际上是在 Spring 5.1.5 中最近实现的。

请看这个问题:https://github.com/spring-projects/spring-framework/issues/15682 - 这是一场斗争:)

现在你实际上可以这样做了:

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

你甚至可以使用这个库:https://github.com/ggeorgovassilis/spring-rest-invoker

获取基于该接口的客户端代理,类似于 RestEasys 客户端框架在 JAX-RS 领域的工作方式。

【讨论】:

    【解决方案3】:

    它适用于较新版本的 Spring。

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

    在Controller中实现接口

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

    它可以用作: Rest client

    【讨论】:

    • 你用带注释的参数测试过这个吗?
    • @Sabuh Das 你用的是什么版本?
    • 春季 4.3.* 有效。但是这个例子中没有参数。 spring-mvc 处理程序适配器不知道接口方法中的任何注释参数。并且有一些解决方法:带有 spring-mvc 注解的接口方法可以有默认实现。此实现委托调用另一个 abstract 方法,该方法由带有@RestController 注释的类实现。在这种情况下,spring 将 http 请求处理程序绑定到具有默认实现的接口方法。 See simple example
    • @TimurMilovanov 您提供的简单示例似乎包含错误。控制器覆盖默认接口方法sayHello 而不是sayHelloImpl。我认为这段代码甚至无法编译。但我尝试了你的意思,解决方法很好;)
    • @GerardBosch 好的,谢谢!固定要点——类覆盖抽象方法。
    【解决方案4】:

    最近我遇到了同样的问题。以下对我有用:

    public class GoalServiceImpl implements GoalService {
        ...
        public void removeGoal(@PathVariableString id) {
        }
    }
    

    【讨论】:

      【解决方案5】:

      我解决了这个问题。

      在客户端:

      我正在使用这个库https://github.com/ggeorgovassilis/spring-rest-invoker/。这个库从接口生成一个代理来调用spring rest服务。

      我扩展了这个库:

      我创建了一个注解和一个工厂客户端类:

      识别 Spring Rest 服务

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

      这个类从接口生成一个客户端休息

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

      我配置我的工厂:

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

      在 REST 服务签名

      这是一个示例界面:

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

      关于 REST 服务实施

      服务实现

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

      为了解决参数上的注释,我创建了一个自定义的 RequestMappingHandlerMapping,它看起来所有使用 @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;
                  }
      
              }
      
          }
      
      }
      

      我创建了一个配置类

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

      我配置了它

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

      【讨论】:

        猜你喜欢
        • 2010-09-14
        • 2010-12-30
        • 1970-01-01
        • 1970-01-01
        • 2014-04-17
        • 2011-12-24
        • 2010-10-04
        • 2015-01-15
        • 2013-02-26
        相关资源
        最近更新 更多