Spring: 相同 class 的多个控制器实例

Spring: Multiple controller instances of same class

JDK版本:1.7(最新更新) Spring:3.2.16-发布

我有一个通用控制器 class,它可以重复用于多种功能。由于基于注释的方法对此类要求的限制,我使用基于 XML 的配置。另外,我在 XML.

中禁用了组件扫描

我配置了相同 class 的多个 bean 实例,并使用 SimpleUrlHandlerMapping 将 URL 映射到控制器。如果我在一次启用一个控制器的情况下测试项目,它工作正常。但是,当我启用第二个实例时,spring 抱怨以下错误:

ERROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'deviceController' bean method 
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'searchController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...
Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'installerController' bean method 
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'deviceController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...

我已经尝试将 scope=singleton 和 scope=prototype 用于控制器 bean 定义。我尝试启用组件扫描(在 XML 中保留手动定义的 bean)并禁用它。错误仍然存​​在。

虽然这可能是固定的,但如果我为每个实例创建具体的 class,我真的想将它保留为最后一个选项。我坚信 Spring 能力,因为我对非控制器 classes 使用了类似的技术。

请告诉我,我缺少什么。

spring 配置(已编辑,控制器为单例)

...
<beans:bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
   <beans:property name="mappings">
      <beans:props>
     <beans:prop key="/">homeController</beans:prop>
     <beans:prop key="/deviceSearch/">deviceController</beans:prop>
     <beans:prop key="/installerSearch/">installerController</beans:prop>
     <beans:prop key="/customerSearch/">customerController</beans:prop>
      </beans:props>
   </beans:property>
</beans:bean>
...
<beans:bean id="homeController" class="com.smvc.pr05.controllers.HomeController" >
</beans:bean>
<beans:bean id="deviceController" class="com.smvc.pr05.controllers.SearchController">
    <beans:property name="metaModel" ref="deviceModel"/>
    <beans:property name="searchService" ref="deviceService" />
</beans:bean>
<beans:bean id="installerController" class="com.smvc.pr05.controllers.SearchController" >
    <beans:property name="metaModel" ref="installerModel"/>
    <beans:property name="searchService" ref="installerService" />
</beans:bean>
<beans:bean id="customerController" class="com.smvc.pr05.controllers.SearchController" >
    <beans:property name="metaModel" ref="customerModel"/>
    <beans:property name="searchService" ref="customerService" />
</beans:bean>

Java 控制器 Class:

...
@Controller
public class SearchController {

    private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);

    private SearchService searchService;    //Has explicit set() method

    private MetaModel metaModel;    //Has explicit set() method

    @SuppressWarnings({ "unchecked" })
    @RequestMapping(method = RequestMethod.POST)
    public String search(Locale locale, ModelMap modelMap) {
        ...
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setMetaModel(MetaModel metaModel) {
        this.metaModel = metaModel;
    }
}

组件扫描似乎仍在运行。因为有人根据 @Controller 注释创建了 SearchController 的实例。这就是为什么你得到 Cannot map 'deviceController' bean method.

另一个问题,如果你在xml配置中使用<mvc:annotation-driven/>,mvc引擎会寻找所有标有@Controller注释的bean,并会尝试根据方法映射这个bean注解。因为你有三个相同 class 的控制器,而这个 class 标记为 @Controller,mvc 引擎将尝试映射所有这些控制器。由于它们将具有相同的方法注释,因此它们将被映射到相同的路径(在您的情况下它是空路径)。这就是为什么你得到 Cannot map 'installerController' bean method.

这两种情况的解决方案:从 SearchController class.

中删除 @Controller 注释

Controller 只是一个构造型注释,它与组件扫描一起工作。这里的罪魁祸首是@RequestMapping,它将所有方法映射到相同的 url。为了使您的配置正常工作,请删除 <mvc:annotation-driven/> 元素,该元素注册了一个使用 @RequestMapping 进行 url 映射的 RequestMappingHandlerMapping bean。现在将使用您的 SimpleUrlHandlerMapping 而不是通过 mvc:annotation-driven 或 @EnableWebMvc

配置的那个

但是您需要注册一个 HandlerAdapter,它知道如何处理 @RequestMapping 方法,即 org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

如下

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

主要问题是,当使用 @Controller<mvc:annotation-driven /> 时,RequestMappingHandlerMappingRequestMappingHandlerAdapter 将启动。第一个将检测所有 @Controller 注释 beans 并基于 @RequestMapping 为它创建一个映射。

因为您已经注册了 3 个相同类型的 bean,它会导致 3 个相同的映射,因此它会停止并告诉您一个异常。基本上随着 RequestMappingHandlerAdapter/RequestMappingHandlerMapping 的引入,使用 SimpleUrlHandlerMapping 的能力和选择方法的注释方式丢失了。

但是您可以删除 <mvc:annotation-driven /> 并添加 AnnotationMethodHandlerAdapter 但是 class 或多或少已被弃用(并且至少会在 [=34= 的未来版本中删除) ]).

我建议使用旧的可靠 Controller 接口而不是注释。您只有一种方法要使用,因此使用旧的支持 classes 是一个可行的选择。

public class SearchController extends AbstractController {

    private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);

    private SearchService searchService;    //Has explicit set() method

    private MetaModel metaModel;    //Has explicit set() method

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception;

        if (!("post".equalsIgnoreCase(request.getMethod()))) {
            return null; // or throw exception or ....
        }
        final Locale locale = LocaleContextHolder.getLocale(); // retrieve current locale.

        ModelAndView mav = new ModelAndView("your-view");
        // prepare your model instead of adding to ModelMap
        mav.addObject("name", object);
        return mav;
    }
    // Omitted setters.
}

这将防止注释扫描启动,并在您升级到删除了已弃用的 class 的 Spring 版本时避免(再次)重构。