带有@PathVariable 的 Spring MVC 注释控制器接口
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8002514/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring MVC Annotated Controller Interface with @PathVariable
提问by willscripted
Is there any reason not to map Controllers as interfaces?
是否有任何理由不将控制器映射为接口?
In all the examples and questions I see surrounding controllers, all are concrete classes. Is there a reason for this? I would like to separate the request mappings from the implementation. I hit a wall though when I tried to get a @PathVariableas a parameter in my concrete class.
在我看到的所有关于控制器的例子和问题中,都是具体的类。是否有一个原因?我想将请求映射与实现分开。但是,当我尝试@PathVariable在具体类中获取 a作为参数时,我碰壁了。
My Controller interface looks like this:
我的控制器界面如下所示:
@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);
}
And the implementing class:
和实现类:
@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);
}
}
The getGoals()method works great; the removeGoal(String id)throws an exception
该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
If I add the @PathVariableannotation to the concrete class everything works as expected, but why should i have to re-declare this in the concrete class? Shouldn't it be handled by whatever has the @Controllerannotation?
如果我将@PathVariable注释添加到具体类,一切都按预期工作,但是为什么我必须在具体类中重新声明它?不应该由任何有@Controller注释的东西来处理吗?
采纳答案by willscripted
Apparently, when a request pattern is mapped to a method via the @RequestMappingannotation, it is mapped to to the concrete method implementation. So a request that matches the declaration will invoke GoalServiceImpl.removeGoal()directly rather than the method that originally declared the @RequestMappingie GoalService.removeGoal().
显然,当一个请求模式通过@RequestMapping注解映射到一个方法时,它被映射到了具体的方法实现。因此,与声明匹配的请求将GoalServiceImpl.removeGoal()直接调用而不是最初声明@RequestMappingie的方法GoalService.removeGoal()。
Since an annotation on an interface, interface method, or interface method parameterdoes not carry over to the implementation there is no way for Spring MVC to recognize this as a @PathVariableunless the implementing class declares it explicitly. Without it, any AOP advice that targets @PathVariableparameters will not be executed.
由于接口、接口方法或接口方法参数上的注解不会延续到实现中,因此@PathVariable除非实现类明确声明,否则Spring MVC 无法将其识别为 a 。没有它,任何以@PathVariable参数为目标的 AOP 建议都不会被执行。
回答by Adam from WALCZAK.IT
The feature of defining all bindings on interface actually got implement recently in Spring 5.1.5.
在接口上定义所有绑定的特性最近在 Spring 5.1.5 中得到了实现。
Please see this issue: https://github.com/spring-projects/spring-framework/issues/15682- it was a struggle :)
请参阅此问题:https: //github.com/spring-projects/spring-framework/issues/15682- 这是一场斗争:)
Now you can actually do:
现在你实际上可以这样做:
@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);
}
}
You can even use this library: https://github.com/ggeorgovassilis/spring-rest-invoker
你甚至可以使用这个库:https: //github.com/ggeorgovassilis/spring-rest-invoker
To get a client-proxy based on that interface, similarly to how RestEasys client framework works in the JAX-RS land.
获取基于该接口的客户端代理,类似于 RestEasys 客户端框架在 JAX-RS 领域的工作方式。
回答by Sabuj Das
It works in newer version of Spring.
它适用于较新版本的 Spring。
import org.springframework.web.bind.annotation.RequestMapping;
public interface TestApi {
@RequestMapping("/test")
public String test();
}
Implement the interface in the Controller
在Controller中实现接口
@RestController
@Slf4j
public class TestApiController implements TestApi {
@Override
public String test() {
log.info("In Test");
return "Value";
}
}
It can be used as: Rest client
它可以用作: 休息客户端
回答by Giancarlo Compagno
i resolved this problem.
我解决了这个问题。
ON CLIENT SIDE:
在客户端:
I'm using this library https://github.com/ggeorgovassilis/spring-rest-invoker/. This library generate a proxy from interface to invoke spring rest service.
我正在使用这个库https://github.com/ggeorgovassilis/spring-rest-invoker/。该库从接口生成代理以调用 spring 休息服务。
I extended this library:
我扩展了这个库:
I created an annotations and a factory client class:
我创建了一个注释和一个工厂客户端类:
Identify a Spring Rest Service
识别 Spring Rest 服务
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpringRestService {
String baseUri();
}
This class generates a client rest from 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);
}
}
I configure my factory:
我配置我的工厂:
<bean class="it.eng.rete2i.springjsonmapper.factory.RestFactory">
<property name="basePackage" value="it.giancarlo.rest.services" />
</bean>
ON REST SERVICE SIGNATURE
休息服务签名
this is an example interface:
这是一个示例界面:
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);
}
ON REST SERVICE IMPLEMENTATION
休息服务实施
Service implementation
服务实现
@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;
}
}
To resolve annotation on parameters I create a custom RequestMappingHandlerMapping that looks all interfaces annotated with @SpringRestService
为了解决参数注释,我创建了一个自定义 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;
}
}
}
}
I created a configuration class
我创建了一个配置类
@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;
}
}
and I configurated it
我配置了它
<bean class="....WebConfig" />
回答by Quang Nguy?n
Recently I had the same problem. Following has worked for me:
最近我遇到了同样的问题。以下对我有用:
public class GoalServiceImpl implements GoalService {
...
public void removeGoal(@PathVariableString id) {
}
}

