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 />
时,RequestMappingHandlerMapping
和 RequestMappingHandlerAdapter
将启动。第一个将检测所有 @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 版本时避免(再次)重构。
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 />
时,RequestMappingHandlerMapping
和 RequestMappingHandlerAdapter
将启动。第一个将检测所有 @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 版本时避免(再次)重构。