看了这一篇Spring容器Bean的生命周期,面试再也不用怕了
前言
上一篇我们介绍了Spring IOC容器的启动过程以及bean的实例化过程,这一篇我们接着来学习另外一个知识点,就是Bean的生命周期,我们知道直接通过(new XX())来创建的实例,当这个实例没有被引用时就会被垃圾回收机制回收,但是通过IOC容器实例化的Bean的生命周期又是如何呢?IOC容器负责管理容器中所有的bean的生命周期,而在bean生命周期的不同阶段,Spring提供了不同的扩展点来改变bean的命运。当然容器只能帮助我们管理单例模式bean的完整生命周期,对于property的bean,Spring在创建好交给使用者之后则不会在管理后续的生命周期。
自定义扩展类
如果我们要自定义一个扩展类,通常需要实现org.springframework.beans.factory.config.BeanFactoryPostProcessor
接⼝,它是Spring对外提供的用来拓展Spring的接口,能够在Spring容器加载了所有bean的信息之后、bean实例化之前执行。他修改bean的定义属性;其中PropertyResourceConfigurer
就是BeanFactoryPostProcessor
的典型应用,PropertyResourceConfigurer
可以将Xml文件中的占位符替换成属性文件中相应key对应的value值。下面以常见的数据库的连接配置举例说明一下:
- Spring的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:mysqldb.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName"value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password"value="${jdbc.password}" />
</bean>
</beans>
- 属性文件—mysqldb.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis
jdbc.username=root
jdbc.password=root
PropertyResourceConfigurer
类的继承结构如下:
上述占位符的例子只是BeanFactoryPostProcessor
的应用之一,但这是Spring提供的拓展,不是我们自定义的,在实际项目中,我们可以通过自定义BeanFactoryPostProcessor
来实现敏感信息的解密处理,例如数据库的连接配置中的,密码我们可以配置成密文,这样就可以防止泄密的风险。解密处理的操作实例如下所示:我们自定义了AESPropertyPlaceholderConfigurer
类继承了PropertyPlaceholderConfigurer
类,然后重写processProperties
方法。
public class AESPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
String key = "jdbc.password";
String aeskey = "0123456789012345";
Object password = props.get(key);
if (password != null) {
try {
props.put(key, AESUtil.decrypt(password.toString(), aeskey));
} catch (Exception e) {
log.warn("数据库密码错误", e);
}
}
super.processProperties(beanFactoryToProcess, props);
}
}
Bean的生命周期
说完了自定义扩展类,下面让我们来看看Bean的生命周期。首先,让我们看一下容器的生命周期以及其管理的Bean的生命周期。图中背景为绿色的部分就是Bean的生命周期。
说明
Bean的完整生命周期经历了各种方法的调用,这些方法可以划分为以下几类:
- Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中
<bean>
的init-method
和destroy-method
指定的方法。 - Bean级生命周期接口方法: 这个包括
BeanNameAware
,InitializingBean
和DiposableBean
这些接口的方法。 - 容器级生命周期接口方法:这个包括了
BeanFactoryProcessor
接口和InstantiationAwareBeanPostProcessor
接口。 - 工厂后处理器接口方法:这个包括了
AspectJWeavingEnabler
,ConfigurationClassPostProcessor
,CustomAutowireConfigurer
等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
流程说明
1. Bean的实例化 (通过构造方法进行实例化)
- Spring对Bean进行实例化(相当于 new XXX()),对于
BeanFactory
一般是延迟实例化,就是说调用getBean
方法才会实例化,但是对于ApplicationContext
,当容器初始化完成之后,就完成了所有Bean的实例化工作。实例化的对象被包装在BeanWrapper
对象中,BeanWrapper
提供了设置对象属性的接口,从而避免了使用反射机制设置属性。
2. InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor
这个接口主要是帮助你在Bean实例化之前做一些操作。它继承自 BeanPostProcessor
接口,其中 postProcessBeforeInstantiation()
方法是在目标对象实例化之前调用的方法,可以返回目标实例的一个代理用来代替目标实例。postProcessPropertyValues
方法是在属性值被设置到目标实例之前调用,可以修改属性的设值。
3. 设置属性(依赖注入)
实例化后的对象被封装到BeanWrapper
对象中,并且此时对象是一个原生状态,并没有执行依赖注入。紧接着,Spring根据BeanDefinition
中的信息进行依赖注入。并且通过BeanWrapper
提供的设置属性的接口完成依赖注入。也就是说第二步就是通过Setter方法设置对象的属性。
4. 注入Aware接口
Spring 会检测该对象是否实现了Aware接口,如果实现了,则通过BeanNameAware的setBeanName方法设置对象名,通过BeanClassLoaderAware的setBeanClassLoader设置对象的加载器,通过BeanFactoryAware设置setBeanFactory设置BeanFactory接口的实现类是AbstractAutowireCapableBeanFactory。
各种各样的Aware接口,其作用就是在对象实例化完成后将Aware接口定义中规定的依赖注入到当前实例中。比较常见的ApplicationContextAware
接口,实现了这个接口的类都可以获取到一个ApplicationContext
对象,当容器中每个对象的实例化过程走到BeanPostProcessor
前置处理这一步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor
,然后就会调用其postProcessorBeforeInitialization()
方法,检查并设置Aware相关的依赖。示例如下:
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
System.out.println("========ApplicationContext配置成功========");
}
}
5. BeanPostProcessor的postProcessBeforeInitialzation方法(前置处理 )
经过上述步骤后,Bean对象已经被正确构造了,如果你想要对象被使用之前在进行自定义的处理,可以通过BeanPostProcessor
接口实现。该接口提供了两个方法
其中postProcessBeforeInitialzation( Object bean, String beanName )
方法;当前正在初始化的bean对象会被传递进来,我们就可以对这个Bean做任何处理,这个方法会先于InitializingBean
执行,因此称为前置处理。
6. InitializingBean与init-method(检查自定义配置)
如果Bean实现了InitializingBean
接口,Spring将调用它们的afterPropertiesSet
方法,作用与在配置文件中对Bean使用init-method
声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。afterPropertiesSet
方法与前置处理不同的是,由于其没有把Bean对象传进来,因此在这一步没有办法处理对象本身,只能增加一些额外的逻辑。
7.BeanPostProcess的postProcessAfterInitialzation方法()
BeanPostProcess
的postProcessAfterInitialzation(Object bean, String beanName )
方法;当前正在初始化的bean对象会被传递进来,我们就可以对这个bean做任何处理。这个函数会在InitializingBean
完成后执行,因此称为后置处理。
8. Bean初始化结束
经过以上的工作以后,Bean的初始化就结束了,Bean将一直驻留在应用上下文中给应用使用,知道应用上下文被销毁。
9. DispostbleBean接口
如果Bean实现了DispostbleBean
接口,Spring将调用它的destroy
方法,作用与在配置文件中对Bean使用destroy-method
属性的作用是一样的,都是在Bean实例销毁前执行的方法。
完整的执行demo示例
总结
本文首先介绍了如何自定义扩展类,对 BeanFactoryProcessor接口的作用做了详细阐述,并介绍了其实现类 PropertyResourceConfigurer,这个实现类的作用就是讲占位符替换成属性文件中对应的属性值,紧接着就是介绍了如果自定义扩展类,通过数据库连接密码解密为例说明。第二部分就是介绍了Spring容器管理的Bean的完整生命周期,如何在面试中回答好Spring中Bean只需要注重如下几个要点,生命周期内要干啥?怎么做的?
要干啥就是:
通过构造器实例化Bean,然后通过Setter方法来设置Bean的属性,那这些做完了,
如果我们要对Bean进行修改该怎么办呢?这就涉及到钩子函数,前置处理和后置处理。
另外我如果要获取Bean的实例该怎么获取呢?例如获取ApplicationContext的实例!那么我们可以通过xxxAware方法,当然需要这个Bean继承 了Aware接口。
最后就是Bean的销毁过程,Bean是通过DispostbleBean接口的destroy方法进行销毁的。
希望本文对读者朋友们有所帮助。
参考
相关文章
- SpringBoot整合Spring Data JPA以及集成Redis
- 手把手教你写一个spring IOC容器
- 深入分析Spring 与 Spring MVC容器
- SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
- Spring Boot + Web Socket 实现扫码登录,这种方式太香了!!
- Spring Boot 如何快速集成 Redis 哨兵?
- Spring Web@ControllerAdvice 配合 @InitBinder 自定义参数绑定
- Spring Data JPA 之 一对一,一对多,多对多 关系映射
- 整合Servlet到Spring容器
- 深入分析Spring 与 Spring MVC容器
- Spring Java-based容器配置
- 超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享
- Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现
- 可以随时拿取spring容器中Bean的工具类
- 将Spring Boot应用程序迁移到Java9:兼容性
- 获取并打印Spring容器中所有的Bean名称
- Spring学习8-Spring事务管理(注解式声明事务管理)
- Spring IOC 容器源码分析
- Spring Cloud Sleuth+ZipKin+ELK服务链路追踪(七)
- Spring Cloud Feign 服务消费调用(三)
- 将Bean放入Spring容器中的五种方式
- spring成神之路第二十四篇:父子容器详解
- spring cloud(二)简单快速的实现负载均衡的功能
- spring mvc
- 使用Spring Native毫秒级启动SpringBoot项目,并且大量减少占用的内存
- Spring Boot(二) :Redis 使用
- Spring Boot与Docker(三):构建你的第一个微服务和相关容器以及容器的连接
- Spring Boot 整合 Shiro ,两种方式全总结!
- Spring源码剖析2:Spring IOC容器的加载过程
- Spring IoC容器初的初始化过程