容器是整個Spring 框架的核心思想缓屠,用來管理Bean的整個生命周期。
一個項(xiàng)目中引入Spring和SpringMVC這兩個框架护侮,Spring是父容器敌完,SpringMVC是其子容器,子容器可以看見父容器中的注冊的Bean羊初,反之就不行滨溉。請記住這個特性。
spring 容器基礎(chǔ)釋義
1
我們可以使用統(tǒng)一的如下注解配置來對Bean進(jìn)行批量注冊长赞,而不需要再給每個Bean單獨(dú)使用xml的方式進(jìn)行配置晦攒。
<context:component-scan base-package="com.amu.modules" />
該配置的功能是掃描配置的base-package包下的所有使用了@Component注解的類,并且將它們自動注冊到容器中得哆,同時也掃描其子類 @Controller勤家,@Service,@Respository這三個注解
2
<context:annotation-config/>
此配置表示默認(rèn)聲明了@Required柳恐、@Autowired伐脖、 @PostConstruct、@PersistenceContext乐设、@Resource讼庇、@PreDestroy等注解。
3
<mvc:annotation-driven />
SpringMVC必備配置近尚。它聲明了@RequestMapping蠕啄、@RequestBody、@ResponseBody等戈锻。并且歼跟,該配置默認(rèn)加載很多的參數(shù)綁定方法,比如json轉(zhuǎn)換解析器等格遭。
4 上面的配置等價于spring3.1之后的版本:
<!--配置注解控制器映射器,它是SpringMVC中用來將Request請求URL到映射到具體Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用來將具體請求映射到具體方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
二:案例分析
2.1 案例初探
Spring容器與其子容器Spring MVC 沖突問題的原因到底在那里哈街?
我們知道,Spring和SpringMVC 容器配置文件分別為applicationContext.xml和applicationContext-MVC.xml拒迅。
1.在applicationContext.xml中配置了<context:component-scan base-package=“com.amu.modules" />
負(fù)責(zé)所有需要注冊的Bean的掃描和注冊工作骚秦。
2.在applicationContext-MVC.xml中配置<mvc:annotation-driven />
負(fù)責(zé)SpringMVC相關(guān)注解的使用。
3.DEBUG 模式下啟動項(xiàng)目璧微,我們發(fā)現(xiàn)SpringMVC無法進(jìn)行跳轉(zhuǎn)作箍,將log的日志打印發(fā)現(xiàn)SpringMVC容器中的請求沒有映射到具體controller中。
4.在applicationContext-MVC.xml中配置<context:component-scan base-package=“com.amu.modules" />
重啟后前硫,SpringMVC跳轉(zhuǎn)有效胞得。
2.2 查看源碼
看源碼SpringMVC的DispatcherServlet,當(dāng)SpringMVC初始化時屹电,會尋找SpringMVC容器中的所有使用了@Controller注解的Bean阶剑,來確定其是否是一個handler。
第1,2兩步的配置使得當(dāng)前springMVC容器中并沒有注冊帶有@Controller注解的Bean嗤详,而是把所有帶有@Controller注解的Bean都注冊在Spring這個父容器中了个扰,所以springMVC找不到處理器,不能進(jìn)行跳轉(zhuǎn)葱色。(結(jié)合上文知識點(diǎn)理解)
核心源碼如下:
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
在源碼isHandler中會判斷當(dāng)前bean的注解是否是controller:
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}
在第4步配置中递宅,SpringMVC容器中也注冊了所有帶有@Controller注解的Bean,故SpringMVC能找到處理器進(jìn)行處理苍狰,從而正常跳轉(zhuǎn)办龄。
原因找到了,那么如何解決呢淋昭?
2.2 解決辦法
在initHandlerMethods()方法中俐填,detectHandlerMethodsInAncestorContexts這個Switch,它主要控制獲取哪些容器中的bean以及是否包括父容器翔忽,默認(rèn)是不包括的英融。
解決辦法:在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts屬性為true(根據(jù)具體項(xiàng)目看使用的是哪種HandlerMapping)盏檐,讓它檢測父容器的bean。
如下:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="detectHandlerMethodsInAncestorContexts">
<value>true</value>
</property>
</bean>
我們按照官方推薦根據(jù)不同的業(yè)務(wù)模塊來劃分不同容器中注冊不同類型的Bean:Spring父容器負(fù)責(zé)所有其他非@Controller注解的Bean的注冊驶悟,而SpringMVC只負(fù)責(zé)@Controller注解的Bean的注冊胡野,使得他們各負(fù)其責(zé)、明確邊界痕鳍。
配置方式如下
1.在applicationContext.xml中配置:
<!-- Spring容器中注冊非@controller注解的Bean -->
<context:component-scan base-package="com.amu.modules">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.applicationContext-MVC.xml中配置
<!-- SpringMVC容器中只注冊帶有@controller注解的Bean -->
<context:component-scan base-package="com.amu.modules" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
小結(jié):
把不同類型的Bean分配到不同的容器中進(jìn)行管理硫豆。
三:進(jìn)階:use-default-filters="false"的作用
3.1 初探
spring-mvc.xml 的不同配置方法
1 只掃描到帶有@Controller注解的Bean
如下配置會成功掃描到帶有@Controller注解的Bean,不會掃描帶有@Service/@Repository注解的Bean笼呆,是正確的熊响。
<context:component-scan base-package="com.amu.modules.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2 掃描到其他注解
但是如下方式,不僅僅掃描到帶有@Controller注解的Bean诗赌,還掃描到帶有@Service/@Repository注解的Bean汗茄,可能造成事務(wù)不起作用等問題。
<context:component-scan base-package="com.amu.modules">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
這是因?yàn)槭裁茨兀?/h5>
3.2 源碼分析
1.<context:component-scan>會交給org.springframework.context.config.ContextNamespaceHandler處理.
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
2.ComponentScanBeanDefinitionParser會讀取配置文件信息并組裝成org.springframework.context.annotation.ClassPathBeanDefinitionScanner進(jìn)行處理境肾。
3.<context:component-scan>的use-default-filters屬性默認(rèn)為true剔难,在創(chuàng)建ClassPathBeanDefinitionScanner時會根據(jù)use-default-filters是否為true來調(diào)用如下代碼:
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
從源碼我們可以看出默認(rèn)ClassPathBeanDefinitionScanner會自動注冊對@Component、@ManagedBean奥喻、@Named注解的Bean進(jìn)行掃描偶宫。
4.在進(jìn)行掃描時會通過include-filter/exclude-filter來判斷你的Bean類是否是合法的:
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;
}
從源碼可看出:掃描時首先通過exclude-filter 進(jìn)行黑名單過濾,然后通過include-filter 進(jìn)行白名單過濾环鲤,否則默認(rèn)排除纯趋。
3.3 結(jié)論
在spring-mvc.xml中進(jìn)行如下配置:
<context:component-scan base-package="com.amu.modules">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
SpringMVC容器不僅僅掃描并注冊帶有@Controller注解的Bean,而且還掃描并注冊了帶有@Component的子注解@Service冷离、@Reposity的Bean吵冒,從而造成新加載的bean覆蓋了老的bean,但事務(wù)的AOP代理沒有配置在spring-mvc.xml配置文件中西剥,造成事務(wù)失效痹栖。因?yàn)閡se-default-filters默認(rèn)為true。
結(jié)論:use-default-filters=“false”禁用掉默認(rèn)瞭空。
【公眾號】:一只阿木木