Spring IoC原理分析
Spring IoC原理分析
问题
要想使用Spring IoC,必须要创建Spring IoC容器。
那么什么是IoC容器呢?
如何创建IoC容器呢?
IoC容器是如何初始化Bean实例的呢?
什么是IoC容器?
什么是IoC容器?
所谓的IoC容器就是指的Spring中Bean工厂里面的Map存储结构(存储了Bean的实例)。
Spring框架中的工厂有哪些?
ApplicationContext接口()
基于了BeanFactory接口,并提供应用框架级别服务
实现ApplicationContext接口的工厂,可以获取到容器中具体的Bean对象
BeanFactory工厂(是Spring框架早期的创建Bean对象的工厂接口)
实现BeanFactory接口的工厂也可以获取到Bean对象,提供了基本的DI支持
其实通过源码分析,不管是BeanFactory还是ApplicationContext,其实最终的底层BeanFactory都是DefaultListableBeanFactory
ApplicationContext和BeanFactory的区别?
创建Bean对象的时机不同:
BeanFactory采取延迟加载,第一次getBean时才会初始化Bean。
ApplicationContext是加载完applicationContext.xml时,就创建具体的Bean对象的实例。(只对BeanDefition中描述为是单例的bean,才进行饿汉式加载)
如何创建Web环境中的IoC容器?
创建方式
- ApplicationContext接口常用实现类
ClassPathXmlApplicationContext:
它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
- Java应用中创建IoC容器:(了解)
ApplicationContext context = new ClassPathXmlApplicationContext(xml路径);
- Web应用中创建IoC容器:(重点)
web.xml中配置ContextLoaderListener接口,并配置ContextConfigLocation参数
web容器启动之后加载web.xml,此时加载ContextLoaderListener监听器(实现了ServletContextListener接口,该接口的描述请见下面《三类八种监听器》)
ContextLoaderListener监听器会在web容器启动的时候,触发contextInitialized()方法。
contextInitialized()方法会调用initWebApplicationContext()方法,该方法负责创建Spring容器(DefaultListableBeanFactory)。
源码分析
- web服务器(tomcat)启动会加载web.xml(启动ContextLoaderListener监听器)
- 调用ContextLoaderListener类的contextInitializd方法,创建Web环境中的Spring上下文对象。
- ContextLoader类中创建Spring容器并初始化容器中的Bean实例
- 继续调用ContextLoader类的configureAndRefreshWebApplicationContext方法,该方法中调用最终初始化Bean的refresh方法
图示:
该图示主要是分析上面第三步中【创建Spring容器】的图示
spring容器初始化源码分析
容器初始化主流程分析
- 主流程入口:
ApplicationContext context = new ClassPathXmlApplicationContext(“spring.xml”)
- ClassPathXmlApplicationContext类:重载的构造方法依次调用,进入下面代码
- AbstractApplicationContext的refresh方法:初始化spring容器的核心代码
- 图示
创建BeanFactory流程分析
获取新的BeanFactory子流程
- 子流程入口(从主流程refresh方法中的第二步开始)
- 调用AbstractApplicationContext中的obtainFreshBeanFactory方法
- 调用AbstractRefreshableApplicationContext的refreshBeanFactory方法
加载解析BeanDefinition子流程(loadDefinitions方法)
源码分析
-
子流程入口(AbstractRefreshableApplicationContext类的方法)
-
此处依次调用多个类的loadBeanDefinitions方法(AbstractXmlApplicationContext
AbstractBeanDefinitionReader
XmlBeanDefinitionReader),一直调用到XmlBeanDefinitionReader
类的doLoadBeanDefinitions方法 -
对于doLoadDocument方法不是我们关注的重点,我们进入到该类的registerBeanDefinitions方法看看
-
此处有两个地方是我们关注的:一个createRederContext方法,一个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入createRederContext方法看看
-
至此,14个NamespaceHandlerResolver初始化成功。然后我们再进入DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法
-
继续进入到该类的doRegisterBeanDefinitions方法看看,这是真正干活的方法
-
继续进入parseBeanDefinitions方法
-
-
我们看到有两种解析方案,先看看parseDefaultElement方法
-
不过我们重点看看BeanDefinitionParserDelegate类的parseCustomElement方法(AOP标签、tx标签的解析都是在该步骤中完成的)
-
getNamespaceURI方法的作用一目了然,我们就不去追踪了,接下来我们进入DefaultNamespaceHandlerResolver类的resolve方法看看:
-
在上面代码中,我们看到了一行代码:namespaceHandler.init();这个方法是很重要的。它实现了自定义标签到处理类的注册工作,不过NamespaceHandler是一个接口,具体的init方法需要不同的实现类进行实现,我们通过AopNamespaceHandler了解一下init的作用,其中aop:config标签是由ConfigBeanDefinitionParser类进行处理:
-
至此,我们了解到了xml中的aop标签都是由哪些类进行处理的了。不过init方法只是注册了标签和处理类的对应关系,那么什么时候调用处理类进行解析的呢?我们再回到BeanDefinitionParserDelegate类的parseCustomElement方法看看
-
我们看到,最后一行执行了parse方法,那么parse方法,在哪呢?我们需要到NamespaceHandlerSupport类中去看看,它是实现NamespaceHandler接口的,并且AopNamespaceHandler是继承了NamespaceHandlerSupport类,那么该方法也会继承到AopNamespaceHandler类中。
-
至此,整个XML文档的解析工作,包括bean标签以及自定义标签如何解析为BeanDefinition信息的过程,我们已经了解了。
-
后续具体想了解哪个自定义标签的处理逻辑,可以自行去查找xxxNamespaceHandler类进行分析。
图示
创建Bean流程分析
-
子流程入口
-
我们进入finishBeanFactoryInitialization方法看看:
-
继续进入DefaultListableBeanFactory类的preInstantiateSingletons方法,我们找到下面部分的代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例的。
-
继续跟踪下去,我们进入到了AbstractBeanFactory类的doGetBean方法,这个方法中的代码很多,我们直接找到核心部分:
-
接着进入到AbstractAutowireCapableBeanFactory类的方法,找到以下代码部分
-
我们终于找到核心的地方了,进入doCreateBean方法看看,该方法我们关注两块重点区域
-
对于如何创建Bean的实例,和填充属性,暂时先不去追踪了,我们先去看看initializeBean方法是如何调用BeanPostProcessor的,因为这个牵扯到我们对于AOP动态代理的理解。
-
至此,如何创建Bean,以及AOP在哪产生代理的步骤,我们已经分析过了。