不管是自己做项目,还是工作中,Spring都是使用最多的框架。但是一直都没有好好的了解一下,本篇博客的目的就是为了深入分析Spring最核心的概念之一:IOC容器的实现原理
通常我们在获取Bean的时候,会这么去做:
//创建Spring容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("classpath*:beans.xml");
//通过getBean()方法获取Bean实例
Person person=(Person) ctx.getBean("person");
我们来看看这个ClassPathXmlApplicationContext里面是怎么样的
下图是ClassPathXmlApplicationContext的继承体系:
下面我们就深入的来看看new ClassPathXmlApplicationContext()是做了什么:
一步步看,先看看super(parent);是在干什么:
1. super(parent):
结尾是在AbstractApplicationContext的构造方法中,来看看这个构造方法
1.1 this():
首先是调用了上面的那个构造器,我们先来看看this.resourcePatternResolover是什么飞机
也就是this的目的其实就是为了给这个resourcePatternResolver变量赋值,我们来看看具体是怎么做的:
这里是直接new PathMatchingResourcePatternResolver(this); 话不多说继续往下点
PathMatchingResourcePatternResolver :
一个ResourcePatternResolver 能够解析指定资源位置到一个或者多个匹配资源的路径,源路径可以是一个简单的路经,它具有到目标为Resource,或者可能包含特殊的 classpath*: 前缀和 / 或者内部Ant-style正则表达式
没有通配符:
在简单情况下,如果指定的位置路经不是以 “classpath*:”为前缀,并且不包含PathMatcher模式,这个解析器将通过getResource()调用基础ResourceLoader。例如 file:C:/context.xml,伪URL 例如: classpath:/context.xml,以及简单的不固定路经 /WEB-INF/context.xml
Ant-style模式:
/WEB-INF/*-context.xml
com/mycompany/*applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/*applicationContext.xml
第一步是设置给parent这个成员变量,这里就不用说了,来看下Environment是个什么类吧
Environment :
表示当前应用程序运行环境的接口,为应用程序环境的两个关键方面建模 profiles 和 properties,与属性访问相关的方法通过PropertyResolver 超级接口。
profile :是要注册的命名的逻辑Bean定义组,只有当给定的配置文件处于活动状态时,才使用容器,可以分配bean到一个配置文件,无论是Xml定义的还是通过注解定义的 或者 @profile 注解,与配置文件相关的Environment对象的角色是确认哪些配置文件(如果有) 当前是getActiveProfiles ,如果没有则 getDefaultProfiles,这里大家应该都有用到过applicationContext-dev.xml,applicationContext-prod.xml 这种选择测试跟线上环境的方法,就是在配置文件中添加
Properties:几乎在所有应用程序中发挥重要作用,并且可能源于各种源: 属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,特殊属性对象,Maps 等等.与属性相关的环境对象的角色是为用户提供方便的环境接口,用于配置属性源并且从中解析属性
ApplicationContext 内管理的bean可注册为 EnvironmentAware 或者 Environment 以便直接查询配置文件状态或解析属性。
然而在大多情况下,application级别的bean不需要与Environment交互,但是必须有 ${...} 属性 由属性占位符配置程序PropertySourcesPlaceholderConfigurer,其本身就是EnvironmentAware,从Spring 3.1开始,在使用时默认注册 <context:property-placeholder/>
环境对象的配置必须通过 ConfigurableEnvironment接口,返回来自所有AbstractApplicationContext子类 getEnvironment 方法
反正这个玩意看图就知道了
到这里,setSuper(parent);这个方法就算是完了。总结一下:
1.在PathMatchingResourcePatternResolver中设置AbstractApplicationContext为resourceLoader
2.AbstractApplicationContext设置ResourcePatternResolver
3. AbstractApplicationContext 设置ApplicationContext
先不要管为啥要这样子设置,看到后面就知道了....
2. setConfigLocations(configLocations):
先不着急说什么,先把前面几个方法给贴上,反正也没几个方法
在我们通过AbstractEnvironmentt的resolveRequiredPlaceholders方法再进入到AbstractPropertyResolver中的过程这里再截图说明一下:
将MutablePropertySources 设置进PropertySourcesPropertyResolver的propertySources属性
这个地方咱们还得回到之前的地方看看
总结:
1.设置AbstractApplicationContext中的 ConfigurableEnvironment environment;
2.解析我们ClassPathXmlApplicationContext传入的资源中的占位符
3、refresh()
好了,终于到了最后的refresh方法了
先从第一个方法开始看吧 ==!
1、prepareRefresh()
首先是设置AbstractApplicationContext的一些初始化变量
initPropertySources(); 在上下文环境中初始化任何占位符属性源,留给子类拓展的
这个方法主要是留给子类或者我们来自己拓展的,其实就是往AbstractPropertyResolver的 requiredProperties集合中添加元素,这个集合是用来存储初始化时,必须存在的环境变量
接下里我们就来手动实验一下这个方法该怎么来玩:
然后我们贴上测试图:
2. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
在Spring源码深度解析这本书中是这么介绍以下这几个类的:
1. BeanFactory: 定义获取bean及bean的各种属性
2. HierarchicalBeanFactory: 继承BeanFactory,也就是BeanFactory定义的功能的基础上增加了对 parentFactory的支持。
3. ListableBeanFactory: 根据各种条件获取bean的配置清单
4. ConfigurableBeanFactory: 根据配置Factory的各种方法
5. ConfigurableListableBeanFactory: beanFactroy配置清单,指定忽略类型以及接口等。
6. DefaultListableBeanFactory:综合上面的所有功能,主要是对bean注册后的处理.
XmlBeanFactory对DefaultListableBeanFactory类进行了拓展,主要是用以从Xml文档中读取beanDefinition,对于注册以及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。
先看看这个getInternalParentBeanFactory()这个方法是在搞什么飞机
接着我们再来看看new DefaultListableBeanFactory的时候干了啥
小结:
new DefaultListableBeanFactory:
1.new AbstractBeanFactory()
2.AbstractBeanFactory中设置parentBeanFactory为ApplicationContext
3.AbstractAutowireCapableBeanFactory的忽略依赖关系接口集合属性中添加了 BeanNameAware,BeanFactoryAware,BeanClassLoaderAware
继续往下看
然后到了最后也是obtainFreshBeanFactory方法最重要的一步了----->loadBeanDefinitions方法,该方法的实现在子类AbstractXmlApplicationContext中,因为这是个抽象方法
我们先进这个new XmlBeanDefinitionReader这个里头看看
这里先说一下EnvironmentCapable这个类(注释翻译):
接口,指示包含并公开Environment引用的组件。
所有Spring应用程序上下文都支持EnvironmentCapable, 并且该接口主要用于在框架方法中执行instanceof检查
这些框架方法接受可能是或可能不是应用程序上下文实例的BeanFactory实例,以便与环境交互(如果确实可用)。
ApplicationContext扩展了环境功能,因此公开了 getenvironment()方法;ConfigurableApplicationContext重新定义了getEnvironment方法 并缩小签名以返回一个ConfigurableEnvironment,其效果是环境对象是“只读”的,直到从ConfigurableApplicationContext访问它为止,此时也可以对其进行配置。
到这里new XmlBeanDefinitionReader(beanFactory); 这个过程就结束了
总结:
1.AbstractBeanDefinitionReader中设置registry属性为DefaultListableBeanFactory
2.AbstractBeanDefinitionReader中设置resourceLoader 为PathMatchingResourcePatternResolver
3.AbstractBeanDefinitionReader中设置environment 为StandardEnvironment
4.设置XmlBeanDefinitionReader的Environment属性为AbstractApplicationContext中的StandardEnvironment (在我们之前super(parent)这一步的时候给new的)
5.设置XmlBeanDefinitionReader的ResourceLoader为AbstractXmlApplicationContext
6.设置XmlBeanDefinitionReader的EntityResolver为ResourceEntityResolver
ResourceEntityResolver: EntityResolver实现,尝试通过ResourceLoader(通常是相对于ApplicationContext的资源库)解析实体引用(如果适用),扩展DelegatingEntityResolver以同时提供DTD和XSD查找。允许使用标准XML实体将XML片段包含到应用程序上下文定义中,例如将大型XML文件拆分为多个模块。include路径可以像往常一样相对于应用程序上下文的资源库,而不是相对于JVM工作目录(XML解析器的默认值)。
注意:除了相对路径之外,在当前系统根目录(即jvm工作目录)中指定文件的每个URL也将相对于应用程序上下文进行解释。
EntityResolver:对于解析一个xml,sax 首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是应为相应的dtd没找到,详细的用法跟说明大家可以看看这个博客:https://www.cnblogs.com/ghgyj/p/4027796.html,我就不再去演示了
initBeanDefinitionReader(beanDefinitionReader):
loadBeanDefinitions(beanDefinitionReader):
这里我们主要是看reader.loadBeanDefinitions(configLocations)这里,首先这个getConfigLocations(),我们在第二步setConfigLocations()的时候,其实就是把我们在new ClassPathXmlApplicationContext时所传的资源付给了AbstractRefreshableConfigApplicationContext的this.configLocations,所以这里只是把我们之前给设置进来的资源给拿出来而已 ==
流程开始:
1. getResourceLoader(): 这一步在new XmlBeanDefinitionReader的时候,就已经把resourceLoader给赋值为PathMatchingResourcePatternResolver这个对象,在上面就总结就可以看得到
2. 因为PathMatchingResourcePatternResolver它实现了ResourcePatternResolver接口,所以这里我们直接看getResources(location)这个方法
PathMatcher : 实现Ant-style的路径模式
映射使用以下规则匹配URL:
?:匹配一个字符
* :匹配零个或多个字符
** : 匹配路径中的零个或多个目录
spring:[a-z]+}: 将regexp {[a-z]+} 作为名为“spring”的路径变量匹配
这里我们就先看这个findAllClassPathResources这个方法:
先来看看这个getClassLoader()方法,这个getResourceLoader也是获取PathMatchingResourcePatternResolver类中的ResourceLoader,在第一步super(parent); 这里我们已经为PathMatchingResourcePatternResolver设置好了resourceLoader
这个地方我们打个断点往下看,这样可以看得更清晰一点:
那么在findAllClassPathResources这个方法的目的就是返回我们资源文件的磁盘路经 ψ(*`ー´)ψ
现在回到我们AbstractBeanDefinitionReader中
我们接着下一个loadBeanDefinitions(resources)这个方法往下看
1.获取当前线程中的EncodedResource集合
2.如果添加失败,则认为是该encodedResource在循环加载
3.获取资源文件InputStream对象
doLoadDocument : 使用配置的DocumentLoader实际加载指定的文档。
这个地方我觉得有必要提一下getEntityResolver这个方法:
接着来看下一个方法
int count = registerBeanDefinitions(doc, resource);
BeanDefinitionDocumentReader:用于解析包含Spring bean定义的XML文档,由XmlBeanDefinitionReader用于实际解析DOM文档。
int countBefore = getRegistry().getBeanDefinitionCount() : 获取bean定义对象Map的大小
getRegistry其实就是获取之前我们给XmlBeanDefinitionReader设置的DefaultListableBeanFactory,是在AbstractXmlApplicationContext类的loadBeanDefinitions方法中new XmlBeanDefinitionReader的时候给赋值了
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)):
先来看看这个createReaderContext方法是做了什么吧:
DefaultNamespaceHandlerResolver:NamespaceHandlerResolver接口的默认实现。根据映射文件中包含的映射将名称空间uri解析为实现类。
ReaderContext (设置属性):
1.resource = Resource
2.problemReporter = FailFastProblemReporter
3. eventListener = EmptyReaderEventListener
4. sourceExtractor = NullSourceExtractor
XmlReaderContext (设置属性):
1. reader = XmlBeanDefinitionReader
2. namespaceHandlerResolver = DefaultNamespaceHandlerResolver
createReaderContext() : 创建XmlReaderContext对象,接下来我们回到registerBeanDefinitions方法:
this.delegate = createDelegate(getReaderContext(), root, parent):
1.创建一个与所提供的相关联的新的BeanDefinitionParserDelegate
this.defaults = DocumentDefaultsDefinition对象 (在标准的Spring XML bean定义文档中,简单的JavaBean包含在<beans>级别指定的默认值:default-lazy-init,default-autowire,etc)
populateDefaults :
到这里populateDefaults这个方法就算是结束了,来个很有仪式感的总结吧: 设置Xml资源文件根节点某些属性默认的值
DocumentDefaultsDefinition :这个使我们传入populateDefaults方法中的this.defaults,该DocumentDefaultsDefinition 是不可变的,所以接下来的设置是有必要给记录下来的
1.DocumentDefaultsDefinition 设置lazyInit为false
2.DocumentDefaultsDefinition 设置merge为false
3.DocumentDefaultsDefinition 设置autowire为no
4.DocumentDefaultsDefinition 设置source 为null
this.readerContext.fireDefaultsRegistered(this.defaults):
调用XmlReaderContext的方法,在父类ReaderContext中找到实现
先打开这个类来看看是咋样的吧
这个地方我可以确定我没有搞错,因为这个地方反正一定是从创建XmlReaderContext的时候给赋值的
那这里就啥都没干....然后返回delegate....那 initDefaults方法就走完了......┓(;´_`)┏.....
接下来是获取beans的profile属性,并且从环境中去对比是否是spring.profiles.active对于的资源文件,这个大家应该都有用过这种方式,我简单说下吧
1.校验文件不为空并且不以 ! 开头
2.获取一个设置好的profile资源集合,下图中有实现方式
3.校验是否存在activeProfiles集合中,或者设置好的profile资源集合为空,校验currentActiveProfiles为空 并且 defaultProfiles集合中存在该profile(doGetDefaultProfiles方法的原理与doGetActiveProfiles一样)
下面我们就写个Demo来玩玩这个profile :
既然是在环境中找,那么我们往环境中加上这个映射不就可以了吗
好了,再回到我们的doRegisterBeanDefinitions方法中接着往下看:
preProcessXml(root):
parseBeanDefinitions(root, this.delegate):
1.import
一开始是判断resource属性是否为空,大家应该也都用过import,我们通常会使用import来分割规划我们的applicationContext资源文件,而resource就是指向这些分割的文件的,那当然是不能为空的(虽然很废话,但还是得说)
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location): 从AbstractBeanDefinitionReader中获取environment,我们在看obtainFreshBeanFactory该步骤的源码中,AbstractBeanDefinitionReader的environment给设置为了StandardEnvironment对象,还有就是resolveRequiredPlaceholders这个方法我们在看setConfigLocations的源码时也说过,这个地方就是在解析我们传入的占位符(${JAVA_HOME}),大家还记得我写的那个demo吗。new ClassPathXmlApplicationContext("classpath*:applicationContext.xml;${JAVA_HOME}");
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute() : 校验该资源路径是否是相对路径或者绝对路径
1.返回给定的资源位置是否是URL:一个特殊的“类路径”或“类路径*”伪URL或一个标准URL。(不等于null并且以classpath*:开头)
2.直接使用URL对象的isAbsolute方法来判断是否是一个绝对路径
Resource[] actResArray = actualResources.toArray(new Resource[0])
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele))
现在来看最后这个地方是在处理啥:
2.alias
首先也不用说了,那就是判断alias标签的两个必须要填的属性是否为空,这个大家肯定是经常都会使用到的,就算现在没有使用过,以前肯定也是使用过的,直接进方法看实现吧
getReaderContext().getRegistry().registerAlias(name, alias):
首先我们知道这里获取的reader是我们的XmlBeanDefinitionReader对象,然后我们调用了它的getRegistry方法,然后就走到了AbstractBeanDefinitionReader中,然后获取它的registry属性,这里我还是要多嘴一句,还是我们在看obtainFreshBeanFactory()方法的源码时给设置的属性,属性值为DefaultListableBeanFactory,实现方法是在SimpleAliasRegistry类中
checkForAliasCircle(name, alias):检查给定名称是否已经指向另一个方向的给定别名作为别名
this.aliasMap.put(alias, name); 添加进alias与 id 的映射
字数太多,没办法发布 ==,后面的写在第二篇,感谢浏览哟~~