在实现接口的控制器上使用 @Controller 的 Spring-MVC 问题

Posted

技术标签:

【中文标题】在实现接口的控制器上使用 @Controller 的 Spring-MVC 问题【英文标题】:Spring-MVC Problem using @Controller on controller implementing an interface 【发布时间】:2010-09-14 07:40:22 【问题描述】:

我正在使用 spring 2.5 和注释来配置我的 spring-mvc 网络上下文。不幸的是,我无法使以下工作。我不确定这是否是一个错误(似乎是这样),或者是否对注释和接口实现子类化的工作方式存在基本误解。

例如,

@Controller
@RequestMapping("url-mapping-here")
public class Foo 
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() 
    ...
  
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() 
  ...
  

工作正常。当上下文启动时,会发现这个处理程序处理的 url,并且一切正常。

但这不是:

@Controller
@RequestMapping("url-mapping-here")
public class Foo implements Bar 
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() 
    ...
  
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() 
  ...
  

当我尝试提取 url 时,我得到以下讨厌的堆栈跟踪:

javax.servlet.ServletException: No adapter for handler [com.shaneleopard.web.controller.RegistrationController@e973e3]: Does your handler implement a supported interface like Controller?
    org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1091)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:874)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:809)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:627)

但是,如果我将 Bar 更改为抽象超类并让 Foo 扩展它,那么它会再次起作用。

@Controller
@RequestMapping("url-mapping-here")
public class Foo extends Bar 
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() 
    ...
  
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() 
  ...
  

这似乎是一个错误。 @Controller 注释应该足以将其标记为控制器,并且我应该能够在我的控制器中实现一个或多个接口,而无需执行任何其他操作。有什么想法吗?

【问题讨论】:

【参考方案1】:

我需要做的是替换

 <tx:annotation-driven/>

 <tx:annotation-driven  proxy-target-class="true"/>

这迫使 aspectj 使用 CGLIB 来执行切面而不是动态代理 - CGLIB 不会丢失注释,因为它扩展了类,而动态代理只公开实现的接口。

【讨论】:

需要注意的是,CGLIB 已被弃用。【参考方案2】:

Ed 是对的,添加

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

工作正常

【讨论】:

【参考方案3】:

如果您希望为 Spring MVC 控制器使用接口,则需要稍微移动注释,如 Spring 文档中所述:http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping

在接口方法上使用@RequestMapping 一个常见的陷阱 应用时会使用带注释的控制器类 需要为控制器对象创建代理的功能 (例如@Transactional 方法)。通常你会引入一个接口 用于控制器以使用 JDK 动态代理。做这个 工作,您必须将 @RequestMapping 注释移动到接口 以及映射机制只能“看到”由 代理。或者,您可以激活 proxy-target-class="true" 在应用于控制器的功能的配置中 (在我们的交易场景中)。这样做 表示应该使用基于 CGLIB 的子类代理,而不是 基于接口的 JDK 代理。有关各种代理的更多信息 机制见第 8.6 节,“代理机制”。

不幸的是,它没有给出具体的例子。我发现这样的设置有效:

@Controller
@RequestMapping(value = "/secure/exhibitor")
public interface ExhibitorController 

    @RequestMapping(value = "/id")
    void exhibitor(@PathVariable("id") Long id);


@Controller
public class ExhibitorControllerImpl implements ExhibitorController 

    @Secured("ROLE_EXHIBITOR")
    @Transactional(readOnly = true)
    @Override
    public void exhibitor(final Long id) 

    

所以你在这里有一个接口,它声明了@Controller、@PathVariable 和@RequestMapping 注释(Spring MVC 注释),然后你可以将@Transactional 或@Secured 注释放在具体类上。由于 Spring 进行映射的方式,您只需要在接口上添加 @Controller 类型注释。

请注意,只有在使用接口时才需要这样做。如果您对 CGLib 代理感到满意,则不一定需要这样做,但如果出于某种原因您想使用 JDK 动态代理,这可能是可行的方法。

【讨论】:

【参考方案4】:

毫无疑问,注解和继承会有点棘手,但我认为这应该可行。尝试将 AnnotationMethodHandlerAdapter 显式添加到您的 servlet 上下文中。

http://static.springframework.org/spring/docs/2.5.x/reference/mvc.html#mvc-ann-setup

如果这不起作用,更多信息会有所帮助。具体来说,这两个带注释的控制器方法是否来自接口? Foo 应该是 RegistrationController 吗?

【讨论】:

【参考方案5】:

我知道为时已晚,但我正在为任何有此问题的人写这篇文章 如果您使用的是基于注释的配置...解决方案可能是这样的:

@Configuration
@ComponentScan("org.foo.controller.*")
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig  ...

【讨论】:

【参考方案6】:

您需要使用 'proxy-target-class="true"' 的真正原因是在 DefaultAnnotationHandlerMapping#determineUrlsForHandler() 方法中:尽管它使用 ListableBeanFactory#findAnnotationOnBean 来查找 @RequestMapping 注释(这需要注意任何代理问题),对@Controller 注释的额外查找是使用AnnotationUtils#findAnnotation 完成的(它不处理代理问题)

【讨论】:

你的意思是有bug吗?

以上是关于在实现接口的控制器上使用 @Controller 的 Spring-MVC 问题的主要内容,如果未能解决你的问题,请参考以下文章

基于Controller接口的控制器及简单应用

5SpringMVC:Controller 详解 及 RestFul风格

5SpringMVC:Controller 详解 及 RestFul风格

Controller控制器

后端SpringMVC Controller(接口定义 & 注解开发)

springMVC学习笔记之Controller控制器