Spring源码
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码相关的知识,希望对你有一定的参考价值。
1.核心监听器ContextLoaderListener实现了ServletContextListener监听项目的启动调用初始化方法
2.调用父类ContextLoader的initWebApplicationContext
3.先初始化当前ApplicationContext对象,通过servletContext对象查看web.xml下是否配置名为contextClass的参数,若没配置就默认初始化XmlWebApplicationContext对象
4.调用ContextLoader的configureAndRefreshWebApplicationContext方法,读取web.xml下配置的contextConfigLocation,随后把servletContext对象设置为XmlWebApplicationContext的一个属性,这样Spring就能在上下文里轻松拿到ServletContext了。最后调用XmlWebApplicationContext的refresh方法,在这个方法里,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作
5.在refresh方法中先构建一个DefaultListableBeanFactory对象,(此对象中维护一个Map<String,Beandefinition>对象来保存Bean的相关信息)
6.接着调用loadBeanDefinitions方法解析配置文件,填充该Map 。
解析的过程:
--首先初始化一个XmlBeandefinitionReader对象(解析配置文件实际靠的是它)
--调用reader对象的loadBeanDefinition,该方法中会利用contextConfigLocation的配置文件路径去读取配置文件,若没该参数则默认去读取/WEB-INF/applicationContext.xml
--XmlBeandefinitionReader拿到配置文件后,通过sax解析先生存document对象,然后解析document下的Element(即解析一个个标签)
--当解析的标签是默认标签时,默认标签是命名空间为(/schema/beans下的标签,<bean>标签),调用document解析代理的parseDeaultElement方法,解析bean的各种属性,如name,class,封装在BeanDefinition对象中,并注册在DefaultListableBeanFactory的map对象里
--当解析的标签不是默认标签时,如命名空间为(/schema/context下的标签,常用的标签如<context:component-scan base-package=""/>),调用document解析代理的parseCustomElement方法,该方法中的NamespaceHandler会根据不同的命名空间,初始化不同的BeanDefinitionParse对象来处理,如<context:component-scan base-package=""/>标签会生成ComponentScanBeanDefinitionParser来扫描指定包及其子包下的有@service、@Compenent等注解的类,把相关的信息封装在BeanDefinition中。
--调用ComponentScanBeanDefinitionParser的parse方法会去base-package的目录及子目录下找到相关的.class文件,并过滤出含有@Service、@Component等注解的类,封装到Beandefinition中
7.把DefaultListableBeanFactory对象设置为XmlWebApplicationContext的一个属性。
至此<bean>或注解扫描配置文件的初始化完毕,最后就是在XmlWebApplicationContext的DefaultListableBeanFactory对象属性中的Map包含了所有要被容器加载的类基本信息
8.根据Map对象里的配置信息初始化成bean对象。具体对应refresh方法中的finishBeanFactoryInitialization方法,该方法用来初始化所有的单例非延迟加载的对象
9.先去DefaultSingletonBeanRegistry(此类为DefaultListableBeanFactory抽象父类)类中的ConcurrentHashMap中寻找是否缓存有beanName对应的单例对象,若有,表明此单例对象已被实例化过,直接返回。
10.若缓存中没找到则再判断当前DefaultListableBeanFactory缓存的Beandefinition的Map的key里是否包含当前的beanName,若没包含并存在父容器的话,则尝试去父容器查找实例化
11.当前DefaultListableBeanFactory缓存的Beandefinition的Map的key里包含该beanName的话,取出对应的BeanDefinition,获取其初始化需要依赖的对象数组,然后循坏初始化依赖的对象。
12.依赖的对象初始化完毕后,判断当前Beandefinition的对象是单例还是多例,两者的区别在于,单例的(Singleton)被缓存起来,而Prototype是不用缓存的。
13.在单例判断分支的createBean方法中,如果没有无参构造器是就生成CGLIB子类,否则就直接反射成实例
14.接着通过populateBean方法注入属性,(属性依赖的对象在11中已全部创建)。
-------几个重要的问题--------
循环依赖问题:对象A依赖对象B,对象B依赖对象A
1.若A、B为单例:
--若A对B的依赖发生在构造参数中,B对A的依赖也在构造参数中,则报错,触发条件:A创建时,会在一个表示正在创建的对象的Map缓存中,保存,当A开始创建时(还未完成构造函数执行)就在表示正在创建的Map中注册自己,执行A的构造函数时,发现依赖B,则去创建B对象,B同理也会在该Map中注册自己,执行B的构造函数时,发现B也依赖于A(此时A没有创建完成),于是去创建A对象,发现A在正在创建缓存中已注册,则报错。【注意,一个对象创建完成后会在该Map中移除】
--若A对B的依赖是属性依赖,通过setter方法注入,B同理,由于对象是先执行构造函数,再执行setter方法,并且执行完构造函数后,则把自己缓存在单例缓存池中,这样调用setter方法时,对象已存在,直接把对象引用赋值给setter即可。不会报错
--基于上述原理,使用setter代替构造器可以完成单例对象的循坏依赖初始化。
2.若A、B为多例:
由于多例对象不存在缓存,所以不能完成循坏依赖初始化,直接报错。
解析AOP标签:
XML解析中,遇到AOP命名空间的标签后,调用parseCustomElement方法,会找到ConfigBeanDefinitionParser来解析AOP标签,
解析完标签后,在bean初始化完后注入属性之前,会检查当前bean是否有切入点,如有切入点,则为当前bean生成代理对象,返回代理对象。
-------------------------------------------精简版--------------------------------------------
1.核心监听器ContextLoaderListener监听web项目的启动,web项目启动时创建IOC容器XMLWebApplicationContext对象,该对象就是IOC容器
【IOC容器对象XMLWebApplicationContext对象里面维护了一个DefaultListableBeanFactory,此对象中维护一个Map<String,Beandefinition>对象来保存Bean的定义相关信息,还维护了单例对象缓存池】
2.然后把servletContext对象设置在该容器中,最后调用XMLWebApplicationContext的refresh方法
3.refresh方法中先创建DefaultListableBeanFactory对象,并且调用loadBeanDefinitions方法解析配置文件中的标签填充到DefaultListableBeanFactory的Map里去,配置文件的位置会尝试去web.xml下配置的contextConfigLocation中读取,若没配置则默认去读取/WEB-INF/applicationContext.xml
4.在解析标签时,不同的命名空间需要用不同的解析类去解析,默认的命名空间是beans,用于解析默认的<bean>标签,解析<bean>的name、class、propterty、constructer等属性,封装在BeanDefinition里,并注册到Map里去
如果遇到<context:component-scan base-package=""/>标签,会调用对象的解析类去base-package指定的包及子包下有component、service、controller、Respositoty的类,并封装成BeanDefinition,保存到Map里。
5.现在XMLWebApplicationContext里的DefaultListableBeanFactory的Map对象里就包含了所有XML配置的bean和包含注解的bean的定义信息。
6.随后开始单例非延迟加载的对象并缓存在缓存池中。
7.每拿到一个BeanName会尝试去单例缓存中直接获取bean实例,若获取不到则创建。
8.创建对象的过程是:取出BeanDefinition,获取依赖对象,会先创建依赖对象的,然后利用反射创建实例对象。当然期间还涉及到解决循环依赖的问题。
【解决循环依赖:若是在构造函数中的循环依赖:创建时,会把自己加到正在创建的缓存中,创建完成会移除,A->B,A加入正在创建,创建B,B加入正在创建,B又依赖A,A创建,A发现自己已经在正在创建的缓存中了,报错】
【 setter,调用完无参构造函数,创建完了之后,不等set属性就把自己提前暴露出去。使其他对象可以引用自己】
9.接着注入属性
以上是关于Spring源码的主要内容,如果未能解决你的问题,请参考以下文章
深度解析Spring源码编译Spring源码(spring5.2.x版本)
spring系统架构源码解析AutowireCandidateResolver