Spring Bean的创建,生命周期总结

129 阅读17分钟

Bean其实就是代指的那些被IOC容器管理的对象

@Component和@Bean的区别

  • @Component作用于类,而@Bean作用于方法
  • @Bean注解比@Component注解更灵活,可增加一些自定义的属性,而且很多时候我们只能使用@Bean,比如引用第三方库中的Jar包通常只能使用此方式来装载成Bean
  • @Component是通过类路径扫描的方式,将所有有标识的类都自动装配到Spring容器中,需要配合@ComponentScan来使用,不然是无法扫描除自身包路径外的其他类的。而@Bean是标注一个方法,方法中通常会直接创建一个实例返回,注入到Spring 容器中。

@Bean使用示例(以下自定义注入是@Component无法实现的 )

@Configuration
public class AppConfig {
	@Bean
	public OneService getService(status) {
    	case (status)  {
        	when 1:
                	return new serviceImpl1();
        	when 2:
                	return new serviceImpl2();
        	when 3:
                	return new serviceImpl3();
    	}
	}
}

Bean作用域

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

如何配置bean作用域

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

Bean 是线程安全的吗

是否线程安全取决于Bean的作用域和状态

如果使用的是prototype作用域,每次获取bean都要创建一个新的bean实例,就不存在资源竞争问题。

而如果使用的默认singleton作用域,IoC容器中只有唯一的实例,可能会存在资源竞争问题。如果bean中成员对象是有状态的,可变的那就存在线程安全问题。

解决方式:

  • Bean中尽量避免定义可变的成员变量
  • 在类中定义一个ThreadLocal成员变量,将可变的变量保存到ThreadLocal中

Bean的创建过程

准备阶段

容器在启动阶段需要做很多预热的操作,为后面的Bean实例化到容器内做好充分的准备

1.配置元信息

通常Bean会有一些成员变量信息,配置元信息两种方式:

一种是xml的方式,如下所示:

<bean id="role" class="com.myBean.springxmlbean.entity.Role">
    <!-- property元素是定义类的属性,name属性定义的是属性名称 value是值
    相当于:
    Role role=new Role();
    role.setId(1);
    role.setRoleName("高级工程师");
    role.setNote("重要人员");-->
    <property name="id" value="1"/>
    <property name="roleName" value="高级工程师"/>
    <property name="note" value="重要人员"/>
</bean>

另一种是Java配置的方式如下所示:

@Configutration
public class BeanTest {
	private String name;
    private Integer value;

    @Bean
    public BeanTest initBean() {
        BeanTest t = new BeanTest();
        t.setName("xx");
        t.setValue(1);
    }
}
2.BeanDefinition

spring内部是用什么样的形式来存储bean的配置信息的呢?答案就是使用BeanDefinition这个对象去存储属性,构造方法参数,bean名称以及bean作用域,延迟加载等bean的元信息。


public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
 
	// 单例、原型标识符
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
 
    // 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
	int ROLE_APPLICATION = 0;
	int ROLE_SUPPORT = 1;
	int ROLE_INFRASTRUCTURE = 2;
 
    // 设置、返回 Bean 的父类名称
	void setParentName(@Nullable String parentName);
	String getParentName();
 
    // 设置、返回 Bean 的 className
	void setBeanClassName(@Nullable String beanClassName);
	String getBeanClassName();
 
    // 设置、返回 Bean 的作用域
	void setScope(@Nullable String scope);
	String getScope();
 
    // 设置、返回 Bean 是否懒加载
	void setLazyInit(boolean lazyInit);
	boolean isLazyInit();
    ...
}
3.BeanDefinationReader

BeanDefinationReader是一个接口,其不同的实现类对不同形式的配置信息进行读取并封装为BeanDefination,下图为结构图:

如上图所示,XmlBeanDefinationReader可以读取xml文件类型的配置信息并存储为BeanDefination。其他Reader以此类推

4.BeanFactoryPostRegistry:BeanDefination的存储仓库

当BeanDefinationReader将配置文件读取并存储到BeanDefination中后,Spring需要通过bean的id寻找到对应的BeanDefination从而获取其配置信息。这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

Spring通过BeanDefinationReader将配置元信息加载到内存生成相应的BeanDefination之后,就将其注册到BeanDefinationRegistry中,BeanDefinationRegistry就是一个存放BeanDefination的仓库,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。

5.BeanFactoryPostProcessor

BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的增强,修改与替换。

比如我们经常使用的占位符的方式来配置,例如JDBC的DataSource连接时可以这样配置:

<bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="maxIdle" value="${jdbc.maxIdle}"></property>
    <property name="maxActive" value="${jdbc.maxActive}"></property>
    <property name="maxWait" value="${jdbc.maxWait}"></property>
    <property name="minIdle" value="${jdbc.minIdle}"></property>

    <property name="driverClassName"
        value="${jdbc.driverClassName}">
    </property>
    <property name="url" value="${jdbc.url}"></property>

    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。

至此第一阶段,准备阶段全部完成了,第一阶段的成果就是注册到BeanDefinitionRegistry中的BeanDefinition

准备阶段总结

实例化阶段

Bean的实例化阶段是开发者可选择的,可以在容器启动则开始加载,也可能会使用懒加载的方式:

  • 懒加载(isLazyInit) :直到我们伸手向Spring要依赖对象实例之前,Bean都是以BeanDefinationRegistry中的一个个的BeanDefination的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。
  • 非懒加载:容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。
1.对象创建策略

对象的创建采用了策略模式,借助我们前面BeanDefinationRegistry中的BeanDefination,我们可以使用反射的方式创建对象,也可以使用CGlib字节码生成创建对象。

2.BeanWrapper——对象外衣

Spring中的Bean并不是直接new 实例化创建的,Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例进行一层封装,类似对象的一件外衣,这个外衣就是BeanWrapper

BeanWrapper实际上是对反射相关API包的一层简单封装,使得上层使用反射完成业务逻辑简化,要获取对象某个属性,调用某个对象方法的话不需要写反射方法,而只需要使用BeanWrapper即可

3.设置对象属性

上一步创建出来的对象还是个空白对象,需要为其设置属性以及依赖对象

  • 基本类型:如果配置元信息中有配置,则直接使用配置元信息中的设置值赋值即可,基本类型的属性没有设置,那么得益于JVM的实例化过程,还是会分配一个默认初始化零值
  • 引用类型:Spring会将所有已经创建好的对象放入一个Map结构中,这时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程
4.检查Aware相关接口

aware接口为Bean对象提供了解Spring容器本身的能力,aware系列接口增强了Spring bean的功能,但是也会造成对Spring框架的绑定,增大了与Spring框架的耦合度。(Aware是“意识到的,察觉到的”的意思,实现了Aware系列接口表明:可以意识到、可以察觉到)

一个Bean对象想要获得spring容器某个部分的引用作为自己成员变量进行使用,就需要实现上述某个接口,并声明相关的成员变量来接收,示例如下:

/**
 * 实现了
 * 	ApplicationContextAware
 *  BeanClassLoaderAware
 *  BeanFactoryAware
 *  BeanNameAware
 *  接口
 * @author dengp
 *
 */
public class User implements ApplicationContextAware,BeanClassLoaderAware,BeanFactoryAware,BeanNameAware{
 
	private int id;
	
	private String name;
	// 保存感知的信息
	private String beanName;
	// 保存感知的信息
	private BeanFactory beanFactory;
	// 保存感知的信息
	private ApplicationContext ac;
	// 保存感知的信息
	private ClassLoader classLoader;
	
	public BeanFactory getBeanFactory() {
		return beanFactory;
	}
 
	public ApplicationContext getAc() {
		return ac;
	}
 
	public ClassLoader getClassLoader() {
		return classLoader;
	}
 
	public User(){
		System.out.println("User 被实例化");
	}
 
	public int getId() {
		return id;
	}
 
	public void setId(int id) {
		this.id = id;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getBeanName() {
		return beanName;
	}
	
	/**
	 * 自定义的初始化方法
	 */
	public void start(){
		System.out.println("User 中自定义的初始化方法");
	}
	
	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", beanName=" + beanName + "]";
	}
 
	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		System.out.println(">>> setBeanClassLoader");
		this.classLoader = classLoader;
	}
 
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println(">>> setApplicationContext");
		this.ac = applicationContext;
	}
 
	@Override
	public void setBeanName(String name) {
		System.out.println(">>> setBeanName");
		this.beanName = name;
	}
 
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println(">>> setBeanFactory");
		this.beanFactory = beanFactory;
	}
}
5.BeanPostProcessor前置处理

目的:BeanPostProcessor前置处理就是在要生产的Bean实例放到容器之前,允许我们程序员对Bean实例进行一定程度的修改,替换等操作。

ApplicationContext对于Aware接口的检查与自动注入就是通过BeanPostProcessor实现的,在这一步Spring将检查Bean

Spring AOP就是在BeanPostProcessor前置这一步实现代理,产生一个增强过的代理对象,然后将源对象的方法调用转嫁到代理对象上。

例子,以下例子会在所有Bean实例化前做控制台输出:

mport org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
 
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("执行MyBeanPostProcessor的postProcessBeforeInitialization()方法...");
		return bean;
	}
 
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("执行MyBeanPostProcessor的postProcessAfterInitialization()方法...");
		return bean;
	}
6.自定义初始化逻辑

初始化有两种方式,实现InitializingBean接口或者配置init-method参数,如果两者同时存在InitiallizingBean接口的afterPropertiesSet()方法,凡是能继承该接口的类,

  1. 配置InitializingBean接口:InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性赋值后初始化的处理方法,它只有一个afterPropertiesSet方法
  2. 配置init-method参数:通过init-method参数指定某个方法为初始化方法,此方法会在Bean初始化时执行
<bean id="myBean" class="com.yuansu.MyBean" init-method="myInit"></bean>
7.BeanPostProcessor后置处理

与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。

8.自定义销毁过程

销毁有两种方式,实现DisposableBean接口或者配置init-method参数

  1. 实现DisposableBean接口:实现其destroy()方法。
  2. 配置destroy-method参数:指定destroy-method方法。

Bean的生命周期

生命周期流程图如下所示:

一旦将Bean交给Spring管理,就会使用BeanFactory和ApplicationContext来创建Bean的实例,然后进行属性初始化操作,其生命周期简单可以划分为(实例化->属性填充->初始化->销毁)四个过程,具体描述如下:

(1)实例化Bean

如果使用的是BeanFactory,那么当客户请求一个尚未初始化的对象时或者在初始化Bean期间需要注入另一个未初始化的依赖,容器就会调用createBean()利用反射原理去实例化Bean;

而ApplicationContext则是在容器启动结束后,一次性将所有Bean实例化。到BeanDefination处获取Bean的信息并实例化所有实例。

(2)设置Bean的属性值

Bean可以通过实现FactoryBean接口的getObject()方法生成一个自定义BeanDefination,会根据BeanDefination定义设置属性值,另外还有BeanWrapper提供的设置属性的接口完成依赖注入

(3)Bean实现Aware相关接口

Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean名字;

Bean实现了BeanClassloaderAware接口,调用setBeanClassLoader(),传入ClassLoader对象的实例

Bean实现了ApplicationContextAware接口调,用setApplicationContext(),传递的是Spring上下文

实现了相关的Aware接口都要在此处进行一个程序执行

(4)BeanPostProcessor的postProcessBeforeInitialization()方法

如果存在加载该bean相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法

(5)如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法

(6)如果Bean配置文件 配置了init-method属性,执行其指定方法

(7) BeanPostProcessor的postProcessAfterInitialization()方法

如果存在加载该bean相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法

至此整个Bean实例化已经完成,可以正常运行

(8)Bean正常运行,需要注入时从容器中获取

(9)销毁Bean

销毁Bean可以先后执行以下两步操作:

    • 如果Bean实现了DisposableBean接口,先调用其destroy()方法
    • 如果在Bean配置文件配置了destroy-method属性,Spring会自动调用其配置销毁方法

注意:

BeanPostProcessor跟InitializingBean作用类似,都是对Bean实例化前作一些特殊处理,但是存在一些区别:

BeanPostProcessor需要额外实现一个处理类,处理范围不限于某个Bean,没有特别指定的话所有Bean实例化都会执行处理类的方法,且可以实现多个处理类;

而InitializingBean只需要在Bean本类上实现即可,处理的范围仅限于当前Bean

BeanFactory和FactoryBean

BeanFactory是用于访问Spring容器的根接口,是从Spring容器中获取Bean对象的基础接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范。通过getBean方法获取IoC容器管理的Bean,ApplicationContext就是其实现类

FactoryBean是为IOC容器中的Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上,给Bean的实现加上了一个简单工厂模式和装饰模式。用户可以通过实现该接口,然后在getObject()方法中灵活配置,来定制实例化Bean的逻辑。 以下是它的源码

public interface FactoryBean<T> {

    //返回的对象实例

    T getObject() throws Exception;

    //Bean的类型

    Class<?> getObjectType();

    //true是单例,false是非单例  

    boolean isSingleton();

}

其中最重要的是getObject方法,从BeanFactory及其实现类的getBean()方法中获取到的Bean对象,实际上是FactoryBean的getObject()方法创建并返回的Bean对象,而不是FactoryBean本身。

在bean生命周期中处于设置对象属性的期间执行,在AOP中广泛应用,其中一个很重要的类ProxyFactoryBean。

@Import和@Bean的区别

@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的元素相同的功能,注解定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
  /**
   * {@link Configuration @Configuration}, {@link ImportSelector},
   * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
   */
  Class<?>[] value();
}

@Import注解非常的简单,只有一个属性value(),类型为类对象数组,如value={class.A,class.C}

@Bean和@Import注入的区别是@Bean 注入没有指定名称的话默认使用方法名,而@Import注入的Bean名称是该实例的全限定类名

如何解决循环依赖

原型(Prototype)模式的场景是不支持循环依赖的,通常走到AbstractBeanFactory类中下面的判断,抛出异常

if(isPrototypeCurrentlyInCreation) {
    throw new BeanCurrentlyInCreationException(beanName);
}

原因很好理解,创建新的A时,发现要注入一个新的B,转头去创建一个新的B,由于循环依赖要注入一个A,所以创建一个新的A,这样会循环套娃,抛出OOM异常,所以Spring先给你抛出一个BeanCurrentlyInCreationException。

Spring解决循环依赖

Spring的DefaultSingletonBeanRegistry类中存在三个Map,相当于三级缓存:

  • singletonObjects:它是单例池最常用的,用于保存已经经过完整生命周期的Bean(元信息配置,依赖注入,初始化完成),属于一级缓存
  • earlySingletonObjects:映射的早期引用,也就是说在这个Map里的Bean是不完整的,只是一个简单引用,属于二级缓存
  • singletonFactories:映射创建Bean的原始工厂,便于后续扩展,属于三级缓存

在实例化Bean的过程中,也就是AbstractFactory.getBean(),getBean有一个getSingleton()

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//从一级缓存中获取
		Object singletonObject = this.singletonObjects.get(beanName);
		//若一级缓存中没有,且当前bean正在创建中
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				//从二级缓存中获取
				singletonObject = this.earlySingletonObjects.get(beanName);
				/**
				 * 二级缓存中没有,且允许循环依赖
				 *二级缓存作用:若涉及到三个及以上对象循环依赖,此时就可以直接从二级缓存中获取到值
				 */
				if (singletonObject == null && allowEarlyReference) {
					//从三级缓存中获取,这里获取到的是一个对象工厂
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						/**
						 * 从对象工厂中获取到一个半成品bean
						 */
						singletonObject = singletonFactory.getObject();
						//放入到二级缓存
						this.earlySingletonObjects.put(beanName, singletonObject);
						//从三级缓存中移除
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

单例模式实例bean流程:

  1. 比如有一个Class A在实例化时它会首先会去一级缓存就是singletonObjects中查找,找不到则进入创建方法BeanFactory.createBean()创建一个Bean,并在singletonFactories加入一个getEarlyBeanReference,表示Class A在创建中
  2. 当创建Bean A的时候发现注入了Class B,所以触发Class B的getBean方法,首先一样的会去一级缓存中去获取,当获取不到会跟步骤1类似进入创建方法,并在singletonFactories加入一个getEarlyBeanReference,表示ClassB在创建中
  3. 当ClassB填充属性的值pouplateBean时,发现依赖了ClassA,会再次触发实例ClassA的流程
  4. 此时已经陷入循环实例化了,首先还是会查找一级缓存,此时singletonObjects中一定获取不到,但是通过判断进入到二级,三级缓存中查找,发现singletonFactories这个Map中找到ClassA,取到后将其放入EarlySingletonObjects,并提供给ClassB填充属性pouplateBean时使用;(此时只是简单引用,不是完整的Bean,没有配置成员信息,依赖注入等等)
  5. ClassB接收到了ClassA的引用,填充属性值完成,此时可以开始创建一个完整的实例了;
  6. 由于ClassB已经完成实例化了,于是ClassA会注入ClassB实例,按照流程创建一个完整的实例
  7. ClassB引用了一个ClassA的地址,所以此时A创建好实例后,ClassB引用的A也因此完成
  8. 实例化完成之后删除早期引用map,并放入单例map中缓存singletonObjects。

无法解决循环依赖的情况

  1. 采用了构造器注入的方式

如果都使用了构造器注入的方式,A依赖B,B依赖A就陷入死循环,由于构造方式定死了无法生成半成品的实例,所以Spring无法解决循环依赖。如果其中有一个是字段注入,且先被Spring扫描到则可以解决

  1. 相互依赖的Bean都是原型Bean(prototype)

两个原型Bean每次创建都需要一个新的实例对象,实例化过程中需要彼此的新对象会陷入死循环

  1. 采用@DependOn注解指定依赖某些Bean

@DependOn主要用于指定当前Bean对象所依赖的其他Bean对象,Spring在创建当前Bean之前要优先创建该注解指定的对象

  1. 使用了@Async注解

在对象A(Spring 先扫描的对象)依赖了B,B中依赖了A,而且在A中使用了@Async注解去调用B,使用@Async时Bean初始化要经历一个后置处理器处理AsyncAnnotationBeanPostProcessor,该处理器会修改对象A的返回结果,生成一个新的代理对象,B注入的A跟这个代理不是同一个对象,所以Spring无法处理