Spring MVC 解读——<context:component-scan/>
转自:http://my.oschina.net/HeliosFly/blog/203149 作者:GoodLoser.
Spring MVC 解读---<context:component-scan/>
注解是骑士魂牵梦绕的美丽公主,也是骑士的无法摆脱的噩梦...
一、<context:component-scan/>
想必@Component,@Repository,@Service,@Controller几个常用的Type-Level的Spring MVC注解,大家都很清楚他们的意思跟用途。标记为@Component的类,在使用注解配置的情况下,系统启动时会被自动扫描,并添加到bean工厂中去(省去了配置文件中写bean定义了),另外三个分别表示MVC三层模式中不同层中的组件,他们都是被@Component标记的,所以也会被自动扫描。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Component//这里。。。public@interfaceRepository {    String value() default"";}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Component//这里。。。public@interfaceService {    String value() default"";}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Component//这里。。。public@interfaceController {    String value() default"";} | 
为了达到以上效果,我们还需在xml配置文件中加入如下定义
| 1 | <context:component-scan base-package="com.springrock..."/> | 
这样Spring就可以正确的处理我们定义好的组件了,重要的是这些都是自动的,你甚至不知道他是怎么做的,做了什么?如果不了解反射,可能真的感到吃惊了,但即便如此,我也想知道它到底做了什么?什么时候做的?
二、BeanDefinitionParser
经过仔细的源码阅读,我找到了这个接口BeanDefinitionParser,文档描述说,它是一个用来处理自定义,顶级(<beans/>的直接儿子标签)标签的接口抽象。可以实现它来将自定义的标签转化为 BeanDefinition类。下面是它的接口定义
| 1 | BeanDefinition parse(Element element, ParserContext parserContext); | 
其中Element是Dom api 中的元素,ParserContext则是用来注册转换来的bean 工厂。
或许你开始恼火说这么多跟上面有什么关系,好吧,下面便是我真正要说的,我们来看下它有哪些实现类:

看到了吧,ComponentScanBeanDefinitionParser,正是我们想要的,他就是用来将<context:component-scan/>标签转化为bean 的解析类。那他做了什么呢?
| 1 2 3 4 5 6 7 8 9 10 11 12 | publicBeanDefinition parse(Element element, ParserContext parserContext) {        String[] basePackages = StringUtils.tokenizeToStringArray(                                            element.getAttribute(BASE_PACKAGE_ATTRIBUTE),                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);        // Actually scan for bean definitions and register them.        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);        returnnull;    } | 
很明显他会获得<component-scan/>的base-package属性,然后解析所需解析的包路径,然后他会创建一个ClassPathBeanDefinitionScanner对象,并委托它来执行对路径下文件的扫描,然后将获得的BeanDefinitions注册到bean工厂中。是不是很清晰?
我想你会急切的知道ClassPathBeanDefinitionScanner 做了什么?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | protectedSet<BeanDefinitionHolder> doScan(String... basePackages) {        Set<BeanDefinitionHolder> beanDefinitions = newLinkedHashSet<BeanDefinitionHolder>();        for(String basePackage : basePackages) {            //这里是重点,找到候选组件            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);            for(BeanDefinition candidate : candidates) {                //.....                //.....                if(checkCandidate(beanName, candidate)) {                    BeanDefinitionHolder definitionHolder =                                             newBeanDefinitionHolder(candidate, beanName);                    beanDefinitions.add(definitionHolder);                    //注册到工厂中                    registerBeanDefinition(definitionHolder, this.registry);                }            }                                }        returnbeanDefinitions;    } | 
重点是继承自父类ClassPathScanningCandidateComponentProvider 的findCandidateComponents方法,意思就是找到候选组件,然后注册到工厂中,那么它是怎么找到候选组件的呢?
我们再看看
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | publicSet<BeanDefinition> findCandidateComponents(String basePackage) {        Set<BeanDefinition> candidates = newLinkedHashSet<BeanDefinition>();        try{            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +                    resolveBasePackage(basePackage) + "/"+ this.resourcePattern;            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);            for(Resource resource : resources) {                if(resource.isReadable()) {                    try{                        MetadataReader metadataReader = this.metadataReaderFactory.                                                                getMetadataReader(resource);                        if(isCandidateComponent(metadataReader)) {                            ScannedGenericBeanDefinition sbd =                                               newScannedGenericBeanDefinition(metadataReader);                            if(isCandidateComponent(sbd)){                                candidates.add(sbd);                            }                        }                    }                }            }        }        returncandidates;    } | 
首先获取路径下的资源Resource,然后判断资源是否可读,并且获取可读资源的MetadataReader对象,然后再调用isCandidateComponent(MetadataReader)判段是否是候选组件,如果是,则生成该metadataReader的ScannedGenericBeanDefinition对象。最后判断ScannedGenericBeanDefinition是否为候选的,如果是则添加到工厂中。
三、includeFilters,excludeFilters
可以看到经历了两次筛选,才找到最终的候选Bean,我们来看第一个过滤做了什么?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | protectedbooleanisCandidateComponent(MetadataReader metadataReader) throwsIOException {        for(TypeFilter tf : this.excludeFilters) {//excludeFilters 是什么?            if(tf.match(metadataReader, this.metadataReaderFactory)) {                returnfalse;            }        }        for(TypeFilter tf : this.includeFilters) {//includeFilters 是什么?            if(tf.match(metadataReader, this.metadataReaderFactory)) {                AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();                if(!metadata.isAnnotated(Profile.class.getName())) {                    returntrue;                }                AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);                returnthis.environment.acceptsProfiles(profile.getStringArray("value"));            }        }        returnfalse;    } | 
我们看到这里有两个实例变量excludeFilters, includeFilters,然后用他们两个去匹配传递进来的MetadataReader,如果与excludeFilter匹配成功返回false, 与includeFilter匹配成功返回true。那么这两个filter分别是什么呢?我们打上断点,调试运行发现

默认情况下includeFilters是一个含有两个值得List,分别是@Component注解和@ManageBean注解,而excludeFilter是个空List,好吧,现在豁然开朗了吧,原来就是它来筛选我们的@Component标记的类。当然我们可以自定义这两个filters,只需在<context:component-scan/>标签下加两个子标签即可, 像这样:
| 1 2 3 4 | <context:component-scan base-package="com.springrock">       <context:exclude-filter type="annotation"expression="org.springframework.stereotype.Repository"/>       <context:include-filter type="annotation"expression="com.springrock.whatever.youcustomized.annotation"/></context:component-scan> | 
四、BeanDefinitionRegistry
上面代码中我们看到还有一个isCandidateComponent方法,它主要是判断当前类是否是具体的,而非抽象类和接口,以及是否是可以独立创建的没有依赖的?鉴于与我们目前讨论的主题不相关,所以略去,感兴趣的话,可以自己查看下源码。
好了,我们既然知道了Spring是怎样通过<context:component-scan/>来扫描,过滤我们的组件了,但是他是怎样将我们定义的组件收集起来供后面的请求处理呢?
我们来看下上面doScan方法中有
| 1 2 | //注册到工厂中registerBeanDefinition(definitionHolder, this.registry); | 
这样一行代码,很明显是将beanDefinition注册到,registry中了。那这个registry是什么呢?是一个BeanDefinitionRegistry,下面是它的接口定义及继承结构:
| 1 2 3 4 5 6 7 8 9 10 | publicinterfaceBeanDefinitionRegistry extendsAliasRegistry {    voidregisterBeanDefinition(String beanName, BeanDefinition beanDefinition)            throwsBeanDefinitionStoreException;    voidremoveBeanDefinition(String beanName) throwsNoSuchBeanDefinitionException;    BeanDefinition getBeanDefinition(String beanName) throwsNoSuchBeanDefinitionException;    booleancontainsBeanDefinition(String beanName);    String[] getBeanDefinitionNames();    intgetBeanDefinitionCount();    booleanisBeanNameInUse(String beanName);} | 

我们可以看到接口中定义了诸多beandefinition的注册,删除,获取等方法,并且Spring为我们提供了三个内部实现,那么运行时,使用了那个实现呢?DefaultListableBeanFactory,是的就是它。它就是SpringMVC 中管理Bean的工厂了,我们来看下,它的registerBeanDefinition是怎样实现的?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | publicvoidregisterBeanDefinition(String beanName, BeanDefinition beanDefinition)            throwsBeanDefinitionStoreException {        synchronized(this.beanDefinitionMap) {            Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);            if(oldBeanDefinition != null) {                if(!this.allowBeanDefinitionOverriding) {                    thrownewBeanDefinitionStoreException();                }                else{                    if(this.logger.isInfoEnabled()) {                        this.logger.info("Overriding bean definition '"+ beanName + "]");                    }                }            }            else{                this.beanDefinitionNames.add(beanName);                this.frozenBeanDefinitionNames = null;            }            this.beanDefinitionMap.put(beanName, beanDefinition);//添加到beanDefinitionMap中了。        }        resetBeanDefinition(beanName);    } | 
从上面的代码可以看出,所有的beanDefinition都由实例变量beanDefinitionMap来保存管理,他是一个ConcurrentHashMap,beanName作为键,beanDefinition对象作为值。到这我们知道了我们的bean是怎样被注册管理的了。但是问题又来了,我们的系统是在什么时候读取<context:component-scan/>标签,并且扫描我们的bean组件的呢?
当然是从ContextLoaderListener开始了入手分析了。
五、ContextLoader
我们查看源码(篇幅问题,不贴代码了,很简答)发现ContextLoaderListener将web application context的初始化动作委托给了ContextLoader了,那ContextLoader做了什么呢?
| 1 2 3 4 5 6 7 | if(this.context == null) {     this.context = createWebApplicationContext(servletContext);}if(this.context instanceofConfigurableWebApplicationContext) {     configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context,                                             servletContext);} | 
上面的代码片段便是ContextLoader中initWebApplicationContext方法中的关键一段。首先会创建一个WebApplicationContext对象,然后configure 并且refresh这个WebApplicactionContext对象,是不是在这个configureAndRefreshWebApplicationContext方法中进行了配置文件的加载和组件的扫描呢?必须是啊。。。
| 1 | wac.refresh(); | 
方法的最后有一个调用了wac的refresh方法,这个wac呢就是前面创建的WebApplicationContext对象,也就是我们这个Web应用的上下文对象。具体是什么呢?我们看一下createWebapplicationContext方法
| 1 2 3 4 5 6 | protectedWebApplicationContext createWebApplicationContext(ServletContext sc) {        Class<?> contextClass = determineContextClass(sc);//这里是关键        ConfigurableWebApplicationContext wac =                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);        returnwac;    } | 
这个方法先确定我们context的类型,调用了determineContextClass方法,
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | protectedClass<?> determineContextClass(ServletContext servletContext) {        //public static final String CONTEXT_CLASS_PARAM = "contextClass";        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);        if(contextClassName != null) {            try{                returnClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());            }        }        else{//defaultStrategies 是关键            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());            try{                returnClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());            }        }    } | 
这个方法先判断我们servletContext中有没有contextClass这个初始化属性(在web.xml的init-param标签中配置),通常我们不会配置这个属性。那肯定是null了,所以它接着去查看defaultStrategy中有没有相应属性,那这个defaultStrategy是什么呢?下面是ContextLoader中一个静态代码块,也就说只要ContextLoader被加载,defaultStrategy便会被赋值。
| 1 2 3 4 5 6 7 8 | static{        try{            //private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";            ClassPathResource resource = newClassPathResource(DEFAULT_STRATEGIES_PATH,                                             ContextLoader.class);            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);        }    } | 
很明显,系统是去ClassPath下读取一个Context.properties的属性文件,并赋值给defaultStrategy,这个属性文件如下:
| 1 2 | org.springframework.web.context.WebApplicationContext                              =org.springframework.web.context.support.XmlWebApplicationContext | 
啊哈,终于找到了,原来是XmlWebApplicationContext啊,这就是我们的WebApplicationContext具体实现对象。
既然找到他了,那我们看看他的refresh()方法做了什么呢?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | publicvoidrefresh() throwsBeansException, IllegalStateException {        synchronized(this.startupShutdownMonitor) {            prepareRefresh();            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();            prepareBeanFactory(beanFactory);            try{                postProcessBeanFactory(beanFactory);                invokeBeanFactoryPostProcessors(beanFactory);                registerBeanPostProcessors(beanFactory);                initMessageSource();                initApplicationEventMulticaster();                onRefresh();                registerListeners();                finishBeanFactoryInitialization(beanFactory);                finishRefresh();            }        }    } | 
五、Bean Factory
这么多代码中,只有第二行与我们当前讨论的主题有关,这一行会尝试获取一个新鲜的BeanFactory,这个BeanFactory与我们之前说的那个BeanDefinitionRegistry有什么关系呢?继续看代码:
| 1 2 3 4 5 | protectedConfigurableListableBeanFactory obtainFreshBeanFactory() {        refreshBeanFactory();        ConfigurableListableBeanFactory beanFactory = getBeanFactory();        returnbeanFactory;    } | 
在getBeanFactory之前,先进行了一个refreshBeanFactory的操作来刷新当前的BeanFactory,我们以此来看一下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override    protectedfinalvoidrefreshBeanFactory() throwsBeansException {        if(hasBeanFactory()) {            destroyBeans();            closeBeanFactory();        }        try{            DefaultListableBeanFactory beanFactory = createBeanFactory();            beanFactory.setSerializationId(getId());            customizeBeanFactory(beanFactory);            loadBeanDefinitions(beanFactory);            synchronized(this.beanFactoryMonitor) {                this.beanFactory = beanFactory;            }        }    } | 
代码依旧很清晰,先判断有没有BeanFactory,如果有,销毁所有Bean,关闭BeanFactory,然后重新创建一个BeanFactory,并将其赋给beanFactory实例变量,有没有发现这个beanFactory是个DefaultListableBeanFactory啊?我们上边讲到的bean definition registry也是个DefaultListableBeanFactory记得吗?他们会不会是同一个呢?答案是yes。重点就在这个loadBeanDefinition(beanFactory)方法上了,很明显:加载Bean Definition到bean工厂中,是不是与我们上边讲到的对上了?
loadBeanDefinition中,Spring会读取xml配置文件,然后会读取里面的bean定义,这一切都是委托给了文章开头的BeanDefinitionParser来完成的,可以看到除了<context:component-scan/>的Parser,还有<mvc:annotation-driven/>的parser,还有<interceptors/>的parser等。是不是比较清晰了?
当然,我们的问题及好奇心远不止这些,这篇文章只是讲解了其中的一小个:系统的初始化做了什么,在什么时候加载我们定义的beans,我们定义的bean被放到了哪里? 等等,现在问题又来了,我们怎样使用我们的bean呢?或者说如果被标记为@Autowire的属性,是怎样被自动装配的呢?@RequestMapping怎样工作的呢?Spring怎样正确调用controller来处理请求呢?等等,后面的文章我们一一解答。
Spring MVC 解读——<context:component-scan/>的更多相关文章
- Spring MVC 解读——<mvc:annotation-driven/>(转)
		转自:http://my.oschina.net/HeliosFly/blog/205343 Spring MVC 解读——<mvc:annotation-driven/> 一.Annot ... 
- Spring MVC 解读——<mvc:annotation-driven/>
		Spring MVC 解读——<mvc:annotation-driven/> 一.AnnotationDrivenBeanDefinitionParser 通常如果我们希望通过注解的方式 ... 
- Spring MVC 解读——@RequestMapping (1)(转)
		转自:http://my.oschina.net/HeliosFly/blog/212329 Spring MVC 解读——@RequestMapping 为了降低文章篇幅,使得文章更目标化,简洁化, ... 
- Spring MVC 解读——@Autowired(转)
		转自:http://my.oschina.net/HeliosFly/blog/203902 Spring MVC 解读——@Autowired 一.@Autowired 作为一个Spring开发者对 ... 
- Spring MVC 解读——@Autowired、@Controller、@Service从原理层面来分析
		目录(?)[+] Spring MVC 解读Autowired 一Autowired 二BeanPostProcessor 三磨刀砍柴 四Bean 工厂 五实例化与装配 六执行装配 七一切的开始 ... 
- Spring MVC 解读——@RequestMapping (2)(转)
		转自:http://my.oschina.net/HeliosFly/blog/214438 Spring MVC 解读——@RequestMapping 上一篇文章中我们了解了Spring如何处理@ ... 
- 【转】Spring MVC 解读——<mvc:annotation-driven/>
		转载自:http://my.oschina.net/HeliosFly/blog/205343 一.AnnotationDrivenBeanDefinitionParser 通常如果我们希望通过注解的 ... 
- Spring MVC 解读——View,ViewResolver(转)
		上一篇文章(1)(2)分析了Spring是如何调用和执行控制器方法,以及处理返回结果的,现在我们就分析下Spring如何解析返回的结果生成响应的视图. 一.概念理解 View ---View接口表示一 ... 
- Spring  MVC 起步
		跟踪Spring MVC的请求 在请求离开浏览器时①,会带有用户所请求内容的信息,至少会包含请求的URL. 请求旅程的第一站是Spring的DispatcherServlet.与大多数基于Java的W ... 
随机推荐
- 【Android】知晓当前是哪一个活动
			首先需要新建一个 BaseActivity 继承自Activity,然后在 BaseActivity 中重写 onCreate()方法,如下所示:public class BaseActivity e ... 
- 二分图的判定hihocoder1121 and hdu3478
			这两个题目都是二分图的判定,用dfs染色比较容易写. 算法流程: 选取一个没有染色的点,然后将这个点染色,那么跟他相连的所有点一定是不同颜色的,所以,如果存在已经染过颜色的,如果和这个颜色相同的话,就 ... 
- JavaScript的DOM操作(一)
			DOM:文档对象模型 --树模型文档:标签文档,对象:文档中每个元素对象,模型:抽象化的东西 一:window: 属性(值或者子对象):opener:打开当前窗口的源窗口,如果当前窗口是首次启动浏览器 ... 
- boost.log要点笔记
			span.kw { color: #007020; font-weight: bold; } code > span.dt { color: #902000; } code > span. ... 
- Js 直接下载保存文件
			//直接下载保存文件 function Download(filePath) { // 如果中间IFRAME不存在,则添加 if (!document.getElementById("_SA ... 
- [Mime] 在c#程序中放音乐的帮助类 (转载)
			using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.M ... 
- js使用
			js使用 HTML 中的脚本必须位于 <script> 与 </script> 标签之间. 脚本可被放置在 HTML 页面的 <body> 和 <head&g ... 
- DataList分页-增加自动编号列
			<asp:DataList ID="dl_XUDAXIA" runat="server"> <HeaderTemplate> <t ... 
- EA UML 建模——类图
			Enterprise Architect(EA) 是一个功能比较强悍的建模工具,本篇文章仅使用其 UML 建模功能,其他更多功能,可以Google. 一.简单梳理C#中类与类.类与接口.接口与接口的关 ... 
- 测试functional的bind以及相关功能
			注:在VS2010 UPDATE1下测试通过 /*测试functional的bind以及相关功能*/ #include <iostream> #include <functional ... 
