说说 Spring Bean 的实例化过程?面试必问


对于写Java的程序员来说,Spring已经成为了目前最流行的第三方开源框架之一,在我们充分享受Spring IOC容器带来的红利的同时,我们也应该考虑一下Spring这个大工厂是如何将一个个的Bean生产出来的。

Spring将管理的一个个的依赖对象称之为Bean,Spring IOC容器就好像一个生产产品的流水线上的机器,Spring创建出来的Bean就好像是流水线的终点生产出来的一个个精美绝伦的产品。既然是机器,总要先启动,Spring也不例外。因此Bean的一生从总体上来说可以分为两个阶段:

  • 容器启动阶段
  • Bean实例化阶段

我们先探讨一下第一个问题:

1.Spring实例化Bean的几种方式

1.1 构造器方式(基于反射实现)

无论我们是通过xml的方式:

<bean id="role" class="com.wbg.springxmlbean.entity.Role"></bean>

还是用过注解,如:@Component等方式,Spring都会通过反射的方式,将Role的完整类路径存在BeanDefination.beanClass属性当中去,那么以后Spring在后面创建bean的时候呢,就会通过这个beanClass进行反射,通过反射来拿到我们的这个实例。

Spring IOC容器将对象实例的创建与对象实例的使用分离,我们的业务中需要依赖哪个对象不再依靠我们自己手动创建,只要向Spring要,Spring就会以注入的方式交给我们需要的依赖对象。

1.2 静态工厂方式(factory-method)

使用这种方式除了指定必须的class属性,还要指定factory-method属性来指定实例化Bean的方法,而且使用静态工厂方法也允许指定方法参数,spring IoC容器将调用此属性指定的方法来获取Bean。

具体过程如下:

  • applicationContext.xml中bean的配置:
<bean id='hello' class="com.wbg.springxmlbean.entity.Hello" 
factory-method="newInstanceHello">
<constructor-arg index="0" value="helloword!"></constructor-arg>
</bean>

我们可以看到多了一个factory-method,我们的Spring则会根绝factory-method中指定的方法newInstanceHello来进行相应的实例化。

public class Hello{
    
    
    public static newInstanceHello(String message){
    
    
        return new Hello(message);
    }
}

使用起来也很简单:

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello=context.getBean("hello",Hello.class);

值得我们注意的是:我们factory-method指定的方法必须是静态,所以叫做静态工厂

1.3 使用实例工厂方法实例化Bean(@Bean)

使用这种方式不能指定class属性,此时必须使用factory-bean属性来指定工厂Bean,factory-method属性指定实例化Bean的方法,而且使用实例工厂方法允许指定方法参数,方式和使用构造器方式一样。

  • applicationContext.xml的bean的配置:
    在这里插入图片描述

与静态工厂不同的是此时factory-method指向的函数不在是静态的,而是普通函数。同时我们还需要在factory-method的基础上,再去指定一个factory-bean
我们的@Bean注解其底层就是以上原理。

无论是factory-bean还是factory-method最终都是将bean定义的相关信息读取到BeanDefination当中。
在这里插入图片描述

我截取部分BeanDedination的信息,我们可以看到如果是基于xml的方式的话,就会将factory-bean以及factory-method存放到该类所对应的这两个字段中,如果是基于@Bean注解的方式,如:

@Configuration
public class WebSocketConfig {
    
    
    @Bean
    public Student student(){
    
    
        return new Student();
    }
}

那么就会把@Bean所在的配置类即WebSocketConfig读取到factoryBeanName中,将@Bean所对应的方法student读取到factoryMethodName当中。

1.4 setter方式

在这里插入图片描述

可以看到我们对People的每一个属性都赋予了setter方法,接下来我们只需要在xml中进行Bean的配置即可。

<bean name="People" class="com.cn.po.People" id="People">
    <Property name="name" value="ninesun"></Property>
    <Property name="age" value="24"></Property>
</bean>

2.容器启动阶段

2.1 配置元信息

上面我们提到了,无论是基于注解,还是基于xml,或者是基于properties配置文件的方式来来存储对象所需要的一些必要的信息(如对象的属性,方法等),我们将这些创建对象所需要的必要信息称为配置元信息

Spring会将这些信息通过我们刚刚反复提到的BeanDefination进行表示。

那么问题来了,Spring是如何看懂这些配置信息并且将它通过BeanDefination进行表示的呢?

这个就要靠我们的BeanDefinationReader了。

不同的BeanDefinationReader就像葫芦兄弟一样,各自拥有各自的本领。

  • 如果我们要读取xml配置元信息,那么可以使用XmlBeanDefinationReader。
  • 如果我们要读取properties配置文件,那么可以使用PropertiesBeanDefinitionReader加载。
  • 如果我们要读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载

总的来说,BeanDefinationReader的作用就是加载配置元信息,并将其转化为内存形式的BeanDefination,这样我们需要创建某一个对象实例的时候,找到相应的BeanDefination然后创建对象即可。

那么我们需要某一个对象的时候,去哪里找到对应的BeanDefination呢?换句话说:BeanDefinationReader将配置元信息转化为BeanDefination之后,会将他们保存至哪里呢?还不好理解,在换句话说:通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?

下面我们的另一个主角亮相:

BeanDefinationRegistry

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

2.2 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做最后的修改,替换$占位符为配置文件中的真实的数据。

至此,整个容器启动阶段就算完成了,容器的启动阶段的最终产物就是注册到BeanDefinationRegistry中的一个个BeanDefination了,这就是Spring为Bean实例化所做的预热的工作。让我们再通过一张图的形式回顾一下容器启动阶段都是搞了什么事吧。
在这里插入图片描述

3.Bean实例化阶段

值得我们注意的是:容器启动阶段与Bean实例化阶段存在多少时间差,Spring把这个决定权交给了我们开发人员。

此时就会出现两种方式:

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

按照这个思路,我们继续向下去探讨一下:

3.1 对象创建策略——策略模式

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

同时我们可以灵活的配置来告诉Spring采用什么样的策略创建指定的依赖对象。

Spring中Bean的创建是策略设计模式的经典应用。这个时候,内存中应该已经有一个我们想要的具体的依赖对象的实例了,但是故事的发展还没有我们想象中的那么简单。

我先大致说一下策略模式的一些核心思想吧,想要了解更多,可以点击此处看看

策略模式设计原则:针对接口编程,而不是针对实现编程

“针对接口编程”关键就在于多态。利用多态,程序可以针对超类型编
程,执行时会根据实际状况执行到真正的行为

同时多用组合,少用继承的设计原则让算法的变化独立于使用算法的客户,因为策略模式定义了算法族,分别封装了起来。

3.2 BeanWrapper——对象的外衣

Spring中的Bean并不是以一个个的本来模样存在的,由于Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例穿上了一层外套,这个外套就是BeanWrapper

BeanWrapper实际上是对反射相关API的简单封装,使得上层使用反射完成相关的业务逻辑大大的简化,我们要获取某个对象的属性,调用某个对象的方法,现在不需要在写繁杂的反射API了以及处理一堆麻烦的异常,直接通过BeanWrapper就可以完成相关操作,简直不要太爽了。

3.3 设置对象属性

上一步包裹在BeanWrapper中的对象还是一个少不经事的孩子,需要为其设置属性以及依赖对象。

对于基本类型的属性,如果配置元信息中有配置,那么将直接使用配置元信息中的设置值赋值即可,即使基本类型的属性没有设置值,那么得益于JVM对象实例化过程,属性依然可以被赋予默认的初始化零值。

对于引用类型的属性,Spring会将所有已经创建好的对象放入一个Map结构中,此时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程。

大家注意了哦,我上面的一段话其实就是在描述Spring如何通过三级缓存来解决循环依赖的问题,这儿讲的比较简单,详情请移步至:《Spring高频面试题—8.Spring怎么解决循环依赖的问题?》

3.4 检查Aware相关接口

我们知道,我们如果想要依赖Spring中的相关对象,使用Spring的相关API,那么可以实现相应的Aware接口,Spring IOC容器就会为我们自动注入相关依赖对象实例。

注意:Aware相关接口有我们耳闻能详的@Autowired、@Resource等注解。

Spring IOC容器大体可以分为两种:

  • BeanFactory提供IOC思想所设想所有的功能,同时也融入AOP等相关功能模块,可以说BeanFactory是Spring提供的一个基本的IOC容器。
  • ApplicationContext构建于BeanFactory之上,同时提供了诸如容器内的时间发布、统一的资源加载策略、国际化的支持等功能,是Spring提供的更为高级的IOC容器。

读到这儿可能有不少同学已经懵了,怎么又是factory-bean又是BeanFactory的,然后还多了个ApplicationContext,这些都是个啥?

首先第一个问题BeanFactory 和FactoryBean这两个东西一样不?

答案:肯定不一样

BeanFactory 和FactoryBean的区别

BeanFactory

是 IOC 容器,并且提供方法支持外部程序对这些 bean 的访问,在程序启动时 根据传入的参数产生各种类型的 bean,并添加到 IOC容器(实现 BeanFactory 接口的类) 的 singletonObject 属性中。

1、负责生产和管理bean的一个工厂。

2、事IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象的依赖。

3、多种实现:如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。

FactoryBean

是个接口,它的实现类首先是个 bean,也存放在 BeanFactory 中。它具有工厂方法的功能,可以让我们自定义Bean的创建过程,并添加到了 IOC容器中的factoryBeanObjectCache 属性中。


讲了这么多,其实就是想表达对于BeanFactory来说,这一步的实现是先检查相关的Aware接口,然后去Spring的对象池(也就是容器,也就是那个Map结构)中去查找相关的实例(例如对于ApplicationContextAware接口,就去找ApplicationContext实例),也就是说我们必须要在配置文件中或者使用注解的方式,将相关实例注册容器中,BeanFactory才可以为我们自动注入。

而对于ApplicationContext,由于其本身继承了一系列的相关接口,所以当检测到Aware相关接口,需要相关依赖对象的时候,ApplicationContext完全可以将自身注入到其中,ApplicationContext实现这一步是通过BeanPostProcessor

3.5 BeanPostProcessor前置处理

再本文2.2节,我们学习了一个名词叫做BeanFactoryPostProcessor,现在又多了一个差不多的叫做BeanPostProcessor,不过其实也好区分,只要记住BeanFactoryPostProcessor存在于容器启动阶段而BeanPostProcessor存在于对象实例化阶段,BeanFactoryPostProcessor关注对象被创建之前 那些配置的修修改改,缝缝补补,而BeanPostProcessor阶段关注对象已经被创建之后 的功能增强,替换等操作,这样就很容易区分了。

BeanPostProcessor与BeanFactoryPostProcessor都是Spring在Bean生产过程中强有力的扩展点。
如果你还对它感到很陌生,那么你肯定知道Spring中著名的AOP(面向切面编程),其实就是依赖BeanPostProcessor对Bean对象功能增强的。

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

前面讲到的ApplicationContext对于Aware接口的检查与自动注入就是通过BeanPostProcessor实现的,在这一步Spring将检查Bean中是否实现了相关的Aware接口,如果是的话,那么就将其自身注入Bean中即可。Spring中AOP就是在这一步实现的偷梁换柱,产生对于原生对象的代理对象,然后将对源对象上的方法调用,转而使用代理对象的相同方法调用实现的。

3.6 自定义初始化逻辑

在所有的准备工作完成之后,如果我们的Bean还有一定的初始化逻辑,那么Spring将允许我们通过两种方式配置我们的初始化逻辑:

InitializingBean

在对象实例化完成后且相关属性和依赖设置完成后,接下来便是初始化过程了,初始化过程主要由 initializeBean函数实现,其主要逻辑如下:

  • 检查Aware相关接口并设置相关依赖
  • 执行BeanPostProcessor的postProcessBeforeInitialization方法(此处会执行@PostConstruct注解方法,且部分Aware接口会在此处处理)
  • 执行InitializingBean接口的afterPropertiesSet方法及init-method方法
  • 执行BeanPostProcessor的postProcessBeforeInitialization方法

配置init-method参数

一般通过配置init-method方法比较灵活。

3.7 BeanPostProcess后置处理

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

3.8 自定义销毁逻辑

这一步对应自定义初始化逻辑,同样有两种方式:

  • 实现DisposableBean接口
  • 配置destory-method参数。

这里一个比较典型的应用就是配置dataSource的时候destory-method为数据库连接的close()方法。

下面以一个Spring生命周期图结束本文:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zhiyikeji/article/details/125590069