(spring-第5回【IoC基础篇】)spring容器从加载配置文件到实例化bean的内部工作机制

前面讲过,spring的生命周期为:实例化前奏-->实例化-->实例化后期-->初始化前期-->初始化-->初始化后期-->bean的具体调用-->销毁前-->销毁。那么,从装配XML属性到实例化bean的内部机制是怎样的,没有细说,今天我们来一起刨根问底。

还是老风格,以具体例子先入为主。下面是一个再简单不过的spring框架的栗子。(XML,有。Bean,有。Spring容器,有。main函数,有。麻雀虽小,但是够了。)

这是XML,简单易懂,嘎嘣脆:

1 。。。。。。
2 
3 <bean id="car" class="com.mesopotamia.test1.Car" 
4          p:brand="宝马X5"
5          p:maxSpeed="200"/>
6 beans>

这是Bean,要个子有个子,要西一翁有西一翁:

复制代码
代码001
 
1 public class Car {
 2     private String name;
 3     private String brand;
 4     private double maxSpeed;
 5     public double getMaxSpeed() {
 6         return maxSpeed;
 7     }
 8     public void setMaxSpeed(double maxSpeed) {
 9         this.maxSpeed = maxSpeed;
10     }
11 
12 
13     private Log log=LogFactory.getLog(Car.class);
14     
15     public Car(){
16         name="宝马";
17         log.info("调用了Car的构造函数,实例化了Car,并把Car的name属性设为:"+name);
18     }
19     public String getName() {
20         return name;
21     }
22     public void setName(String name) {
23         this.name = name;
24     }
25     public String getBrand() {
26         return brand;
27     }
28     public void setBrand(String brand) {
29         this.brand = brand;
30     }
31     
32     
33     public String toString(){
34         return "名字"+name+" 型号"+" 速度:"+maxSpeed;
35     }
36     
37 
38 }
复制代码

下面是启动函数,精悍!干练:

复制代码
代码002

1 public class Main {
2     private static Log log=LogFactory.getLog(Main.class);
3     
4     public static void main(String args[]){
5         ApplicationContext ctx = new ClassPathXmlApplicationContext("com/mesopotamia/test1/*.xml");
6         Car car1 = ctx.getBean("car",Car.class);
7         log.info(car1.toString());
8     }
复制代码

但是我要讲的重点是main函数跑起来后的日志:

复制代码
代码003

1 2016-11-25 20:19:04,318  INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1ff5ea7: startup date [Mon Nov 16 20:19:04 CST 2015]; root of context hierarchy
2 2016-11-25 20:19:04,371  INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from file [C:\MySoftware\workspace\springtest\WebRoot\WEB-INF\classes\com\mesopotamia\test1\beans.xml]
3 2016-11-25 20:19:04,482  INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6e293a: defining beans [car]; root of factory hierarchy
4 2016-11-25 20:19:04,483  INFO [main] (Car.java:22) - 调用了Car的构造函数,实例化了Car,并把Car的name属性设为:宝马
5 2016-11-25 20:19:04,533  INFO [main] (Main.java:15) - 名字宝马 型号 速度:200.0
复制代码

一开始就加载了AbstractApplicationContext里的方法,那么这个方法做了什么?实际上,第一行是由AbstractApplicationContext的refresh()方法打印出的,容器一启动就要加载这个方法,让我们来揭开refresh()的面纱吧。

首先,这个AbstractApplicationContext必须是ClassPathXmlApplicationContext的父类,否则代码002的第5行怎么一跑起来会跑到AbstractApplicationContext里面的方法里去?孩子被打,当然是去叫爹咯。

在MyEclipse里,我们按住ctrl键,点击ClassPathXmlApplicationContext一路点下去你就发现其中的继承关系(姑且用—>表示子类指向被继承的父类):

ClassPathXmlApplicationContext —> AbstractXmlApplicationContext —>AbstractRefreshableConfigApplicationContext —>AbstractRefreshableApplicationContext—>AbstractApplicationContext。

我们进入AbstractApplicationContext,看到refresh()方法的庐山真面目:

复制代码
 1 public void refresh() throws BeansException, IllegalStateException {
 2         synchronized (this.startupShutdownMonitor) {
 3             // Prepare this context for refreshing.
 4             prepareRefresh();
 5 
 6             // Tell the subclass to refresh the internal bean factory.
 7             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 8 
 9             // Prepare the bean factory for use in this context.
10             prepareBeanFactory(beanFactory);
11 
12             try {
13                 // Allows post-processing of the bean factory in context subclasses.
14                 postProcessBeanFactory(beanFactory);
15 
16                 // Invoke factory processors registered as beans in the context.
17                 invokeBeanFactoryPostProcessors(beanFactory);
18 
19                 // Register bean processors that intercept bean creation.
20                 registerBeanPostProcessors(beanFactory);
21 
22                 // Initialize message source for this context.
23                 initMessageSource();
24 
25                 // Initialize event multicaster for this context.
26                 initApplicationEventMulticaster();
27 
28                 // Initialize other special beans in specific context subclasses.
29                 onRefresh();
30 
31                 // Check for listener beans and register them.
32                 registerListeners();
33 
34                 // Instantiate all remaining (non-lazy-init) singletons.
35                 finishBeanFactoryInitialization(beanFactory);
36 
37                 // Last step: publish corresponding event.
38                 finishRefresh();
39             }
40 
41             catch (BeansException ex) {
42                 // Destroy already created singletons to avoid dangling resources.
43                 destroyBeans();
44 
45                 // Reset 'active' flag.
46                 cancelRefresh(ex);
47 
48                 // Propagate exception to caller.
49                 throw ex;
50             }
51         }
52     }
复制代码

人生若只如初见,看到这如此美丽的代码是否惊呆了天真烂漫的你?OK,我知道你英语没我好,我就大概讲一下这refresh()里面都干了些什么,这些步骤实际上就是实例化之前的一系列美丽动作:

第9行:准备bean factory  (BeanFactory是spring框架的基础设施)

第16行:调用被注册为bean的BeanFactoryPostProcessors(工厂后处理器)  (工厂后处理器负责对实例化之前未成形的bean进行加工处理)

第19行:注册BeanPostProcessors(Bean后处理器)来阻挡bean的创建。  (实例化后的bean需要用这个后处理器来进一步加工)

第22行:初始化消息源(国际化信息资源)。  (国际化很容易理解吧?比如微信可以切换中英文版本等等,后面会独列篇幅讲解)

第25行:初始化应用上下文事件广播器。  (spring有一套完善的事件发布和监听机制,事件广播器负责把事件通知给监听器,监听器来执行事件。后面会独列篇幅讲解)。

第28行:初始化其他特殊的bean。

第31行:检查是否有监听器然后注册监听器(监听器就跟bean一样,需要放在注册表中。后面会独列篇幅讲解)。

第34行:初始化所有单实例的bean(懒模式bean除外),单实例的bean初始化后把bean的引用放在spring容器的缓存中,调用者使用的是同一个额引用,任何一个调用者对bean的修改都会影响其他调用者。懒模式是指,spring容器启动时不会初始化,而在需要用到该bean时才初始化。XML标签中的lazy-init属性就是设置懒模式或者勤快模式的,false是勤快模式,true是懒模式。

第37行:创建上下文刷新事件,发布广播器。  (事件机制后面会独列篇幅讲解)

 

上面这些就是bean实例化前后的细枝末节了。那么上面的什么工厂后处理器,bean后处理器被调用后又是怎样处理的呢?

下面我们来细化一下创建一个完整的bean的作业流程:

(spring-第5回【IoC基础篇】)spring容器从加载配置文件到实例化bean的内部工作机制_第1张图片

整体是下面这样的:

简单点:

读取XML,转化并加工成BeanDefinition,实例化BeanDefinition。

具体点:

ResourceLoader加载XML配置信息后,由BeanDefinitionReader读取配置信息文件,把每个解析成BeanDefinition对象保存在注册表中。容器首先扫描注册表取出工厂后处理器,对注册表中的BeanDefinition进行加工处理。Spring容器接着从注册表中取出加工过的BeanDefinition开始着手bean实例化的事情。实例化时,首先由BeanWapper对bean进行封装,配置属性。最后利用注册表中的Bean后处理器装饰打扮,装配出一个准备就绪的Bean来。(注册表就类似于一个Map,把所有的bean,不管是业务bean,还是spring自己的bean,都放到注册表里,用的时候取出来)。

 

再具体点:

  1. ResourceLoader加载XML配置信息后,由BeanDefinitionReader读取配置信息文件,把每个解析成BeanDefinition对象保存在BeanDefinitionRegistry注册表中。这时的BeanDefinition可能只是个半成品,因为某些XML属性配置里会有占位符变量,这些变量此时不会被解析出来,需要继续优化,比如下面这样:
    复制代码
    1    <bean id="simpleBean" class="com.spring.ch04.SimplePostProcessor">  
    2    <property name="connectionString" value="${simpleBean.connectionString}"/>  
    3    <property name="password" value="${simpleBean.password}"/>  
    4    <property name="username" value="${simpleBean.username}"/>  
    5      
    6    bean>  
    复制代码

     

  2. 因为有1的情况出现,所以容器首先扫描注册表取出工厂后处理器,对注册表中的BeanDefinition进行加工处理,把占位符替换成真正的值,产生成品的BeanDefinition。
  3. 通过反射机制扫描BeanDefinitionRegistry所有属性编辑器的bean类,并把这些bean放到spring容器的属性编辑器注册表(PropertyEditorRegistry)中。(Spring的属性编辑器负责将配置文件中的文本配置值转换为Bean属性的配置值,这个后面会独辟章节来讲)。
  4. Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手对bean的实例化工作。(这里的实例化只是相当于new了一个新对象一样,也就是说,只是跑一个构造函数,不会具体的为属性设置值,当然如果构造函数里写了设置值的语句,那么也可以赋值。比如一开始的那个例子,实例化时在构造函数里就给Car的name属性附上了"宝马"的名字)。
  5. 实例化的过程中,spring容器使用BeanWrapper对bean进行封装,BeanWrapper结合BeanDefinition和属性编辑器注册表中的属性编辑器完成bean的属性设置工作。
  6. 最后调用Bean后处理器对bean 作最后的润色。

你可能感兴趣的:(Spring,Spring解析)