带有 @PathVariable 的 Spring MVC 带注释的控制器接口
Posted
技术标签:
【中文标题】带有 @PathVariable 的 Spring MVC 带注释的控制器接口【英文标题】:Spring MVC Annotated Controller Interface with @PathVariable 【发布时间】: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 小时超时到期后我会发布我的解释 【参考方案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" />
【讨论】:
以上是关于带有 @PathVariable 的 Spring MVC 带注释的控制器接口的主要内容,如果未能解决你的问题,请参考以下文章
带有 Spring Boot Rest 服务的 Multipart