IOC容器的初始化过程:
Spring的IoC容器初始化包括:Bean定义资源文件的定位、载入和注册3个基本过程。
在上篇讲过,IOC容器的启动是调用了refresh()方法,所以,在这里,我们来看看refresh()的流程:
我们知道,在IOC容器内部的数据结构是BeanDefinition--------->所以--->我们要把XML文件中定义的Bean---------->转换成----------->BeanDefinition
基于这个要求,我们可以把refresh()这个启动方法大致分为三个基本过程:
1、找资源(Resource定位过程)--->BeanDefinition的资源定位---->ReourceLoader通过统一的Resource接口来完成
2、载入资源(BeanDefinition的载入)--->把用户定义好的Bean表示成IoC容器内部的数据结构(BeanDefiniiton)
3、注册资源(IoC容器注册这些BeanDefinition)---------->(IOC容器注册这些BeanDefinition)
注意:IOC容器初始化过程中不包含Bean依赖注入的实现。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。
例外:如果我们对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IoC容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才会触发。
BeanDefinition的Resource定位:
1、首先定义一个Resource来定位容器使用的BeanDefinition。
ClassPathResource res = new ClassPathResource("bean.xml");--------------------(用ClassPathReource意味着会在类路径中去寻找)
2、这里定义的Resource还不能由DefaultListableBeanFactory直接使用,还要经过BeanDefinitionReader进行处理
3、经过处理的Resource资源才能由DefaultListableBeanFactory使用。
所以---------在ApplicationContext中--------提供了一系列加载不同Resource的读取器的实现
例:FileSystemXMLApplicationContext、ClassPathXMLApplicationContext、XMLWebApplicationContext等。
一个是从文件系统载入Resource,一个是从类路径中载入Resource,还有一个可以在Web容器中载入。
例:FileSystemXMLApplicationContext对Resource的定位:
从图中可以看出-------FileSystemXmlApplicationContext---继承------AbstractXmlApplicationContext
而进入源码去看,AbstractXmlApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。
因为-----AbstractXmlApplicationContext----继承---------AbstractApplicationContext
而AbstractApplicationContext----------基类------------DefaultResourceLoader
FileSystemXmlApplicationContext的实现:
1、解释:什么是双亲IoC容器?---------子容器可以引用父容器的Bean,但是父容器不能引用子容器的Bean,并且各个子容器中定义的bean是互不可见的,这样也可以避免因为不同的插件定义了相同的bean而带来的麻烦。
应用场景:插件或组件的接入,只需要对方提供JAR即可,由父容器进行引导,各个子容器再完成自己的应该完成的工作即可。
2、通过重载的构造函数,实现了对configuration进行处理的功能,让所有配置在文件系统中的,以XML文件方式存在的BeanDefinition都能够得到有效的处理。
既可以使用一个字符串来配置多个Spring Bean定义资源文件,也可以使用字符串数组,即下面两种方式都是可以的:
a. ClasspathResource res = new ClasspathResource(“a.xml,b.xml,……”);
多个资源文件路径之间可以是用” ,; /t/n”等分隔。
b. ClasspathResource res = new ClasspathResource(newString[]{“a.xml”,”b.xml”,……});
因为到最后都是通过同一个构造方法去处理资源文件。
3、super(parent);----------->调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器:
我们进入到最后调用父类的构造方法是AbstractApplicationContext:
this();---------->调用了自身的构造函数:
setParent();---------->设置父容器,如果,父容器不为空,并且根据当前环境得到相应数据(即激活不同的Profile,可以得到不同的属性数据,比如用于多环境场景的配置(正式机、测试机、开发机DataSource配置)),存放在Environment对象中。
而在自身的构造函数中获取一个Spring Source的加载器用于读入Spring Bean定义资源文件:
4、设置完父容器,得到Spring的资源加载器之后,需要通过setConfigLocations():设置Bean定义的资源文件,完成IoC容器Bean定义资源的定位:
this.configLocations[i] = resolvePath(locations[i]).trim();------------->resolvePath为同一个类中将字符串解析为路径的方法
先将资源文件的路径都存放到AbstractRefreshableConfigApplicationContext的configLocations变量中,等在加载BeanDefinitions的时候调用getResourceByPath取出来。
而相应的过程就是在refresh()---->obtainFreshBeanFactory()--->refreshBeanFactory()----->loadBeanDefinitions()----->getResources()
5、getResourceByPath():这个方法是一个模板方法,在父类中定义,为读取Resource服务的,这里覆盖父类DefaultResourceLoader的方法,通过Bean定义文件路径封装得到IoC容器要读入的定位Bean定义的资源
getResourceByPath大致调用流程:
refreshBeanFactory:
createBeanFactory():首先通过createBeanFactory构建了一个IoC容器供ApplicationContext使用,这个IoC容器就是DefaultListableBeanFactory。
customizeBeanFactory():对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等
loadBeanDefinitions():调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器。
loadBeanDefinitions:
getReource:
在getResource方法中,最后返回的是调用了getResourceByPath方法,说明这个方法到最后是把获取资源的任务交给getResourceByPath。
getResourceByPath:
前面我们看到的getResourceByPath方法会被FileSystemXmlApplicationContext实现,这个方法返回的是一个FileSystemResource对象,通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位。
注意:如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,例:ClassPathResource、ServletContextResource等。
重点:关于IoC容器功能相关的实现,这里是没有涉及的,因为它继承了AbstractXmlApplicationContext,关于IoC容器功能相关的实现,都是在这个类中实现的,在构造函数中通过refresh()来直接启动容器,这样用户可以直接通过实例化FileSystemXmlApplicationContext来启动容器。
总结:在启动过程中,调用了refreshBeanFactory方法,启动并定位Resource资源,到最后把执行定位Resource任务委托给了FileSystemXmlApplicationContext中的getResourceByPath方法。