Spring MVC 里加载两次Bean的解决办法
Contents
SpringMVC 里上下文的概念
web.xml
里的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>uniweibov2</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在SpringMVC里,有两种上下文。一种是:Application Context
,还有一种是Web Application Context
.
application context (parent)
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
这样的配置,就是在配置application context
,也就是parent application context
web application context (child)
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
parent 是共享于 child的,也就是说,在parent中定义的东西,都可以在child中使用.
Bean加载两次的原因:
这是由于在这两个context里,配置的时候导致Spring扫描时,扫描并加载了两次Bean.
例如,在这两个spring.xml
和spring-servlet.xml
配置文件里,都配置了:
<context:component-scan base-package="xx.yy" />
这样子,就会导致Spring加载了两次上下文环境。
解决办法
方法一
只使用一个上下文环境。即Bean的定义,只放在一个配置文件里,让另一个配置文件为空,即如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
</beans>
这两个,只要一个为空,另一个不为空即可.
方法二
在这两个配置文件里,只是没有配置重复的内容即可.即如果在parent里定义了的Bean,就不要在child里定义了。这时,可以使用如下的方式来配置:
在spring.xml
里配置:
<context:annotation-config />
<context:component-scan base-package="xx.yy">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
这样子,就表明扫描除了有@Controller
之外的所有注解的Bean.
在spring-servlet.xml
里配置:
<mvc:annotation-driven />
<!-- Scans for annotated @Controllers in the classpath -->
<context:component-scan base-package="xx.yy" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan
这样子,就表明只扫描@Controller
注解的Bean.注意这里的user-default-filters="false"
包扫描详解
<context:component-scan base-package="xx.yy" use-default-filters="false">
base-package
:要扫描的包
use-default-filters
:是否使用默认的过滤器,默认为true
,即扫描@Component, @Repository, @Service, @Controller
这些注解的Bean
context:include-filter
:使用白名单过滤器
context:exclude-filter
:使用黑名单过滤器
Spring 的使用顺序是: 先 exclude-filter
,再到include-filter
。
在源码org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
的方法里:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
if (!metadata.isAnnotated(Profile.class.getName())) {
return true;
}
AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
return this.environment.acceptsProfiles(profile.getStringArray("value"));
}
}
return false;
}
type的类型还有:
type=`annotation`
type=`assignable`
type=`aspectj`
type=`regex`
type=`custom`