《Spring揭秘》---- IoC容器及Bean的生命周期

Posted coder为

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Spring揭秘》---- IoC容器及Bean的生命周期相关的知识,希望对你有一定的参考价值。

  Spring的IoC容器会以某种方式加载配置信息,然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。实现以上功能,分为两个阶段:容器启动阶段和Bean实例化阶段。而且Spring的IoC容器在每个阶段都加入了相应的扩展点,以便根据具体场景的需要加入自定义的扩展逻辑。

  1 容器启动阶段

  首先会通过某种途径加载配置信息,大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的配置信息进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDifinitionRegistry,这样容器启动工作就完成了。

  该阶段所作工作是准备性的,重点更加侧重于对象管理信息的收集,以及一些验证性的和辅助性的工作。

 

  2 Bean实例化阶段

  现在所有的bean定义信息都已经注册到了BeanDefinitionRegistry中,当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发bean实例化。

  在这一阶段,容器会先检查所请求的对象之前是否已经初始化,如果没有,会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。

 

  3 干预容器启器

  Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制,允许在容器实例化对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

  可以通过两种方式来应用BeanFactoryPostProcessor, 分别针对基本的IoC容器BeanFactory和较为先进的容器ApplicationContext.

  对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor:

ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));

PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();

propertyPostProcessor.setLocation(new ClassPathResource("..."));
propertyPostProcessor.postProcessBeanFactory(beanFactory);

 

  对于ApplicationContext来说,它会自动识别配置文件中的BeanFactoryPostProcessor并应用它,所以仅需要在XML中将这些BeanFacotryPostProcessor简单配置一下即可。

<beans>
    <bean    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations"> 
       <list>
        <value>conf/jdbc.properties</value>
        <value>conf/mail.properties</value> </list> 
    </property>
    </bean>
... 
</beans>

 

Spring提供的几个BeanFactoryPostProcessor

  • PropertyPlaceholderConfigurer

   PropertyPlaceholderConfigurer允许在XML配置文件中使用占位符,并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。以数据源的配置为例:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="url">
      <value>${jdbc.url}</value>
  </property>
  <property name="driverClassName">
    <value>${jdbc.driver}</value>
  </property>
  <property name="username">
    <value>${jdbc.username}</value>
  </property>
  <property name="password">
    <value>${jdbc.password}</value>
  </property>

   <property name="maxActive">
    <value>100</value>
   </property>

< /bean> 

jdbc.properties文件如下: 

jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932& 
failOverReadOnly=false&roundRobinLoadBalance=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=your username
jdbc.password=your password

原理:

  当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符表示的属性值。这样,当进入容器实现的第二个阶段实例化bean时,bean定义中的属性值就是最终替换完成后的了。

  PropertyPlaceholderConfigurer不仅会从其配置的properties文件中加载配置项,同时还会检查System类中的Properties. PropertyPlaceholderConfigurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK/SYSTEM_PROPERTIES_MODE_NEVER/SYSTEM_PROPERTIES_MODE_OVERRIDE三种模式,默认采用FALLBACK,即如果properties文件中找不到相应配置项,则到System的properties中查找。

  • PropertyOverrideConfigurer

  PropertyOverrideConfigurer可以对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。比如前一个例子中的dataSource,maxActive值为100, 如果想把这个值覆盖掉,改成200,就可以在一个properties文件中配置:

dataSource.maxActive=200

  所以如果要对容器中某些bean的property信息进行覆盖,需要按照如下规则提供一个PropertyOverrideConfigurer使用的配置文件:

beanName.propertyName=value

也就是说,properties文件中的键是以XML中配置的bean定义的beanName为标志开始的(通常就是id指定的值),后面跟着相应被覆盖的property的名称。如下是PropertyOverridConfigurer在XML中的配置信息:

<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
  <property name="location" value="pool-adjustment.properties"/>
</bean>

当容器中配置的多个PropertyOverrideConfigurer对同一个bean定义的同一个property值进行处理的时候,最后一个会生效。

 

  4 Bean的生命周期

 转载自 http://www.jianshu.com/p/3944792a5fff

 

  • ApplicationContext Bean生命周期

 

 

1. Bean的实例化

  首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化。

  容器在内部实现的时候,采用“策略模式”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。默认情况下,容器内部采用CglibSubclassingInstantiationStartegy。容器只要根据相应bean定义的BeanDefinition取得实例化信息,结合CglibSubclassingInstantiationStartegy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。这个BeanWrapper的实现类BeanWrapperImpl是对某个bean进行包裹,然后对包裹后的bean进行操作,比如设置或获取bean的相应属性值。

2. 设置对象属性

  BeanWrapper继承了PropertyAccessor接口,可以以同一的方式对对象属性进行访问,同时又继承了PropertyEditorRegistry和TypeConverter接口,然后BeanWrapper就可以很方便地对bean注入属性了。

3. 如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该bean的id,此时该Bean就获得了自己在配置文件中的id。

4. 如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的BeanFactory,这样该Bean就获得了自己所在的BeanFactory.

5. 如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext, 这样该Bean就获得了自己所在的ApplicationContext.

6. 如果有一个Bean实现了BeanPostProcessor接口,并将该接口配置到配置文件中,则会调用该接口的postProcessBeforeInitialization()方法。

7.如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法。

8. 如果Bean配置了init-method方法,则会执行init-method配置的方法。

9. 如果有一个Bean实现了BeanPostProcessor接口,并将该接口配置到配置文件中,则会调用该接口的postProcessAfterInitialization方法。

10.经过9之后,就可以正式使用该Bean了,对于scope为singleton的Bean, Spring IoC容器会缓存一份该Bean的实例,而对于scope为prototype的Bean, 每次被调用都回new一个对象,而且生命周期也交给调用方管理了,不再是Spring容器进行管理了。

11. 容器关闭后,如果Bean实现了DisposableBean接口,则会调用该接口的destroy()方法。

12. 如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean生命周期结束。

 

示例

我们定义了一个Person类,该类实现了BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean五个接口,并且在applicationContext.xml文件中配置了该Bean的id为person1,并且配置了init-method和destroy-method,为该Bean配置了属性name为jack的值,然后定义了一个MyBeanPostProcessor方法,该方法实现了BeanPostProcessor接口,且在applicationContext.xml文件中配置了该方法的Bean

 

Person.class

package com.ivy.beanlifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class Person implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean{

    private String name;
    
    public Person() {
        System.out.println("Person constructor");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("setter() invoked");
    }
    
    public void myInit() {
        System.out.println("myInit() invoked");
    }
    
    public void myDestroy() {
        System.out.println("myDestroy() invoked");
    }

    @Override
    public void setBeanName(String beanName) {
        System.out.println("setBeanName() invoked, beanName : " + beanName);
        
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("setBeanFactory() invoked, beanFactory : " + beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        System.out.println("setApplicationContext() invoked");
        
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet() invoked");
        
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy() invoked");
        
    }
    
    public String toString() {
        return "Person[name=" + name +"]";
    }
}

 

MyBeanPostProcessor.class

package com.ivy.beanlifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor{

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessAfterInitialization() invoked, beanName : " + beanName);
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        System.out.println("postProcessBeforeInitialization() invoked, beanName : " + beanName);
        return bean;
    }

}

 

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd">
            
    <bean id="person1" class="com.ivy.beanlifecycle.Person" init-method="myInit" destroy-method="myDestroy">
        <property name="name" value="ivy"></property>
    </bean>
    
    <bean id="myPostProcessor" class="com.ivy.beanlifecycle.MyBeanPostProcessor"></bean>
</beans>

 

PersonServiceApplicationContextTest.class

package com.ivy.beanlifecycle;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class PersonServiceApplicationContextTest{

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        System.out.println("start init ioc container");
        ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("end loading xml");
        Person person = (Person)aContext.getBean("person1");
        System.out.println(person);
        System.out.println("close container");
        ((ClassPathXmlApplicationContext)aContext).close();
    }

}

 

运行结果:

start init ioc container
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
Person constructor
setter() invoked
setBeanName() invoked, beanName : person1
setBeanFactory() invoked, beanFactory : org.springframework.beans.factory.support.DefaultListableBeanFactory@5fa12d: defining beans [person1,myPostProcessor]; root of factory hierarchy
setApplicationContext() invoked
postProcessBeforeInitialization() invoked, beanName : person1
afterPropertiesSet() invoked
myInit() invoked
postProcessAfterInitialization() invoked, beanName : person1
end loading xml
Person[name=ivy]
close container
destroy() invoked
myDestroy() invoked

  可以看出,在加载xml的时候ApplicationContext就实例化了所有的bean

 

  • BeanFactory Bean生命周期

 

 BeanFactoty容器中, Bean的生命周期如上图所示,与ApplicationContext相比,有如下几点不同:

1. BeanFactory容器中,不会调用ApplicationContextAware接口的setApplicationContext()方法

2. BeanPostProcessor接口的postProcessBeforeInitialization方法和postProcessAfterInitialization方法不会自动调用,必须自己通过代码手动注册

3. BeanFactory容器启动的时候,不会去实例化所有bean,包括所有scope为singleton且非延迟加载的bean也是一样,而是在调用的时候去实例化。

 

还是以上边Person为示例

PersonServiceBeanFactoryTest.class

package com.ivy.beanlifecycle;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class PersonServiceBeanFactoryTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        System.out.println("start init ioc container");
        ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        System.out.println("end loading xml");
        beanFactory.addBeanPostProcessor(new MyBeanPostProcessor());
        Person person = (Person)beanFactory.getBean("person1");
        System.out.println(person);
        System.out.println("close container");
        beanFactory.destroySingletons();
    }
}

 

运行结果:

start init ioc container
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
end loading xml
Person constructor
setter() invoked
setBeanName() invoked, beanName : person1
setBeanFactory() invoked, beanFactory : org.springframework.beans.factory.xml.XmlBeanFactory@ccd65d: defining beans [person1,myPostProcessor]; root of factory hierarchy
postProcessBeforeInitialization() invoked, beanName : person1
afterPropertiesSet() invoked
myInit() invoked
postProcessAfterInitialization() invoked, beanName : person1
Person[name=ivy]
close container
destroy() invoked

  可以看出,end loading xml之后才实例化的person。

 

以上是关于《Spring揭秘》---- IoC容器及Bean的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

sping揭秘4某些无法注册到IOC容器的对象如何交给spring托管

Spring基础IOC容器及常见注解

死磕 Spring----- IOC 之开启 bean 的加载

[死磕 Spring 16/43] --- IOC 之开启 bean 的加载

Spring系列之IOC容器

Spring IOC容器解析及实现原理