zoukankan      html  css  js  c++  java
  • (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:MySoftwareworkspacespringtestWebRootWEB-INFclassescommesopotamia est1eans.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<bean>标签中的lazy-init属性就是设置懒模式或者勤快模式的,false是勤快模式,true是懒模式。

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

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

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

    整体是下面这样的:

    简单点:

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

    具体点:

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

    再具体点:

    1. ResourceLoader加载XML配置信息后,由BeanDefinitionReader读取配置信息文件,把每个<bean>解析成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 作最后的润色。

    话音到此戛然而止。洗洗睡吧。

      知者不惑,仁者不忧,勇者不惧。

                   ----子曰

  • 相关阅读:
    【Leetcode】23. Merge k Sorted Lists
    【Leetcode】109. Convert Sorted List to Binary Search Tree
    【Leetcode】142.Linked List Cycle II
    【Leetcode】143. Reorder List
    【Leetcode】147. Insertion Sort List
    【Leetcode】86. Partition List
    jenkins 配置安全邮件
    python 发送安全邮件
    phpstorm 同步远程服务器代码
    phpUnit 断言
  • 原文地址:https://www.cnblogs.com/mesopotamia/p/4970370.html
Copyright © 2011-2022 走看看