Spring:同一个类的多个控制器实例

时间:2016-03-09 09:21:32

标签: java spring spring-mvc controller xml-configuration

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

我有一个通用的控制器类,可以重用于多种功能。由于基于注释的方法对此类需求的限制,我使用的是基于XML的配置。此外,我已禁用XML中的组件扫描。

我已经配置了同一个类的多个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)并禁用它。错误仍然存​​在。

虽然这可能是固定的,但如果我为每个实例创建具体类,我真的希望将它作为最后一个选项。我对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控制器类:

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

3 个答案:

答案 0 :(得分:1)

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

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

然而,您可以删除<mvc:annotation-driven />并添加AnnotationMethodHandlerAdapter但是该类或多或少已被弃用(并且至少在将来的Spring版本中将被删除)。

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

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.
}

当您升级到删除已弃用类的Spring版本时,这将阻止注释扫描进入,并使您免于重构(再次)。

答案 1 :(得分:0)

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

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

两种情况的解决方案:从@Controller类中删除SearchController注释。

答案 2 :(得分:0)

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

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

如下

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