In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans。
什么是bean?在Spring中,被Spring ioc容器管理的实例,称作为bean。那么bean的声明周期是怎样的呢?本文带你认识Spring bean声明周期的标准答案。
生命周期
直接说答案太枯燥了,我们来做个比喻。
假如你现在进了饭馆,你想吃的菜厨师不会做,你这时候就得教他怎么做这道菜!那么流程是什么呢?
给后厨看菜谱--->后厨研究一下--->大厨们根据自己经验修改一下--->开始炒菜--->放佐料--->放其他佐料--->放盐--->放其他佐料--->出锅--->消灭掉
类似一下就是spring bean的生命周期:
定义--->解析--->修改定义--->实例化--->属性赋值--->初始化前置增强--->初始化--->初始化后置增强--->使用--->销毁
Bean定义
做菜第一步,告诉厨师怎么做!
菜谱:BeanDefinition,Bean定义,最常见方式有两种,xml方式和java注解方式。
Bean定义就是声明Spring如何进行创造对象的,如果Spring是厨师,那么Bean定义就是菜单。
一个bean定义有以下9种属性:
-
Class :定义如何bean的class。
-
Name:定义bean的名称
-
Scope:定义bean的作用域
-
Constructer Arguments:定义使用的构造方法
-
Properties:属性
-
Autowired Mode:注入模型
-
Lazy initialization mode:懒加载
-
Initialization method:初始化方法
-
Destruction method:销毁方法
下面着重讲一下几个关键的bean定义属性
Class
定义一个bean实例化成为哪个类。可能会有读者觉得,我平常就是写@Service @Controller @Component,bean的class就是写死的,难道还会故意把bean定义成为其他的class吗?
我们先来看一个有趣的特性。如下代码演示:
这里我们先定义Deer,也就是鹿
/**
* This is deer.
*/
public class Deer {
public Deer(){
System.out.println("Dear constructs");
}
}
然后定义Horse类,马
/**
* This is horse.
*/
public class Horse {
public Horse(){
System.out.println("Horse constructs");
}
}
下面代码可以先不用管:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition definitionClass = (GenericBeanDefinition) beanFactory.getBeanDefinition("deer");
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
definitionClass.setBeanClass(Horse.class);
}
}
然后这是我们的测试代码,以下代码是构造一个spring容器,并注册一个Deer类的bean,然后把上面的那个MyBeanFactoryPostProcessor注册进去。
public class TestApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Deer.class);
context.register(MyBeanFactoryPostProcessor.class);
context.refresh();
System.out.println(context.getBean("deer").getClass());
}
}
我们预期应该是添加的Deer类的bean,获取到的bean的class类应该是deer。
看下实际结果。运行程序后,打印日志如下:
class com.example.spring.bean.lifecycle.beanclass.Horse
竟然打印出的是Horse,这不是指鹿为马吗?!
再联想一下,mybatis为什么只写Mapper接口就能进行dao操作?我们知道接口是无法实例化的。
没错,mybatis就是在这点进行了扩展,通过修改mapper的bean定义中的class属性,把mapper的class修改成了一个FactoryBean,从而实现了mapper的代理类。
这个现象比较有趣,这里其实我们主要使用了BeanFactoryPostProcessor去修改了Bean定义,即使注册的bean是Deer,但是我们修改成了Horse。关于BeanFactoryPostProcessor的内容请往后看。
这就是bean定义的class属性。
Autowired Mode(注入模型)
注入模型分为四种,分别是
no:不注入,这也是默认的选项。意味着如果我们不在某个字段上标记@Autowired等标签,spring就把该字段当做是普通字段,不会进行自动注入。
byName:在容器中寻找和属性同名的bean进行注入。
byType:在容器中寻找字段的class的bean并注入。
constructor:和byType相似,只不过是在构造函数上注入。
可能说的太抽象,我们来举个例子说明下:
我们首先定义一个最最简单的Class,稍后我们把它加到spring容器中。
public class Blue {
}
然后我们声明另一个类,Green,Green里面有一个Blue属性。
public class Green {
private Blue blue;
public Green(){
System.out.println("Constructs with no arg");
}
public Green(Blue blue){
System.out.println("Constructs with a blue arg");
this.blue = blue;
}
public Blue getBlue() {
return blue;
}
public void setBlue(Blue blue) {
this.blue = blue;
}
}
默认情况下,我们将这两个类添加到spring容器中,Green的bean中是不会注入Blue的,因为我们没有添加@Autowired。
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Green.class);
context.register(Blue.class);
// context.register(AutowiringModeChangeBeanFactoryPostProcessor.class);
context.refresh();
Green bean = context.getBean(Green.class);
System.out.println(bean.getBlue());
}
}
运行程序,打印输出
符合我们预期。
然后我们借助实现上一个例子介绍的BeanFactoryPostProcessor,来对Green的bean定义进行一些修改,将它的注入模型改为byType。
public class AutowiringModeChangeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanFactory.getBeanDefinition("green");
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
然后我们再来测试一下。
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Green.class);
context.register(Blue.class);
context.register(AutowiringModeChangeBeanFactoryPostProcessor.class);
context.refresh();
Green bean = context.getBean(Green.class);
System.out.println(bean.getBlue());
}
}
输出结果变成了
发现Green的bean中的blue字段不为空。
之所以会实现这个效果,其实就是我们讲的,我们把Green的这个bean的注入模型改成了byType,spring就在容器中寻找blue的bean,然后注入到属性中。所以Green 中的blue不为空。
Bean扫描
以上spring的bean定义,对应了我们说的菜谱。那么下一步,厨师要理解你的菜谱了。
这一步我们成为扫描,或者解析bean定义。
什么是解析?就是扫描我们的bean定义然后添加到spring容器中。
这里我先把答案放出来。我们的bean定义最后都会放到BeanFactory的beanDefinitionMap中去。
然后我们打断点进去看一看是怎么添加进去的
查看调用栈,一步步进行添加到BeanDefinitionMap中去。xml解析同样的原理,就是把xml配置解析成bean定义。如果有需求,甚至可以自己定义txt解析去添加bean定义。解析不展开描
BeanFactoryPostProcessor
接下来,厨师可以稍微修改下你的菜谱。
在我们第一节,讲bean定义的时候,我们提到过BeanFactoryPostProcessor。
在spring容器启动时,加载我们的bean定义后,来到spring提供的第一个扩展点!那就是BeanFactoryPostProcessor。
BeanFactoryPostProcessor允许我们读取bean的配置信息,然后在spring初始化bean之前去修改bean定义。
只需要实现该接口,并添加到容器中,spring就会执行实现的方法,进行你需要的增强。
BeanFactoryPostProcessor只有一个方法需要实现 postProcessBeanFactory,在这个方法中我们可以根据传递过来的beanfactory来获取想要修改的bean定义。正如我们之前修改的注入模型和class时候做的。
实例化
接下来开始炒菜!
实例化意味着执行构造方法。
在refresh方法的finishBeanFactoryInitialization方法中,会对所有的单例非懒加载的bean进行创建,实例化便是其中步骤之一。
首先会先去获取bean,不存在就创建bean,解决循环依赖等。这里我们不展开描述,这不影响我们今天的专题。
如果你不知道bean是在哪个方法实例化的,那么你就打断点在构造方法上,毕竟只要实例化,无论是反射也好new也罢,都会走构造方法。
我们代码如下:
public class Green {
private Blue blue;
public Green(){
System.out.println("Constructor with no arg invoked");
}
public Blue getBlue() {
return blue;
}
public void setBlue(Blue blue) {
this.blue = blue;
}
}
跟踪断点:
调用栈信息如下:
也就是说Green的构造是在
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
这个方法中执行的。
注意实例化是初始化的区别,实例化仅仅是调用构造方法进行对象的创建,不包含属性的赋值等初始化方法。
属性赋值
属性赋值,我类比为放佐料。
在上一步对象构造完之后,对象内的属性还没有进行赋值。
毫无疑问,下一步就是赋值操作,例如讲Blue注入到Green里面去。
该方法就在creatBeanInstance方法的下面。
经过该步骤后,green的blue就赋值成功了,可以根据断点看。
具体的populate步骤,有兴趣可以自己跟下源码,我这里把获取green的那行代码放出来,小伙伴们可以自己打断点进去看看。
在org.springframework.beans.factory.support.DefaultListableBeanFactory#
doResolveDependency 方法中解析依赖,然后进入到下图的断点中:
值得注意的是我们之前测试注入模型的时候,设置的注入类型byType或者byName也是在populateBean里面处理的。代码如下:
看不懂没关系,这些个小细节先省略。我们知道这一步是进行属性赋值就行!
下面的步骤该是什么呢?属性也赋值完毕了,还要干什么?
初始化
这里我把初始化前置增强,初始化,初始化后置增强,统一归为初始化。
类似炒菜的比喻,就是放其他佐料,放盐,放其他佐料。
自定义初始化的操作,这里我们还可以进行扩展,通过实现BeanPostProcessor接口。
利用BeanPostProcessor可以自定义逻辑应用于修改bean。换句话说,也就是在bean实例化,赋值操作完毕后,允许我们通过这个接口在调用init方法之前和之后进行一些操作。
当然init方法我们也是可以定义!可以通过实现InitializingBean接口,或者在初始化方法上添加@PostConstruct注解。
BeanPostProcessor有两个方法
postprocessBeforeInitialization()
postProcessAfterInitialization()
见名知意,分别是在init之前和之后的方法。
执行的源码如下:
这不就是初始化前置增强(放其他佐料),初始化(放盐),初始化后置增强(放其他佐料)!
接下来我们来验证一下。
首先我们先实现一个BeanPostProcessor,什么操作都不做,只是打印一下信息。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before initialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after initialization");
return bean;
}
}
然后实现InitializingBean 接口或者使用@PostConstruct注解。
public class Green {
private Blue blue;
@PostConstruct
public void init(){
System.out.println("post construct");
}
public Green(){
System.out.println("Constructs with no arg");
}
public Green(Blue blue){
System.out.println("Constructs with a blue arg");
this.blue = blue;
}
public Blue getBlue() {
return blue;
}
public void setBlue(Blue blue) {
this.blue = blue;
}
}
可以进行断点进行查看执行时机,篇幅原因,我这里只打印结果,感兴趣小伙伴自己断点调试效果更佳。
结果如下:
证实了我关于初始化的理论,before,init,after。
也就是执行
-
BeanPostProcessor的postProcessBeforeInitialization()方法
-
然后执行自定义的@PostConstruct方法
-
然后执行postProcessAfterInitialization()方法
至此,bean的创建就结束了。
至于像其他什么ApplicationContextAware,BeanNameAware,没什么特别的意义,有兴趣自己打断点看一下就行了。
剩下就是在容器运行期间,进行bean的一些使用了。
销毁我不准备讲,不是重头戏!可以自己测试@PreDestroy注解,相同的套路,打断点调试!
最后我们来系统走一遍:
-
bean定义
-
bean定义被spring解析
-
post process bean factory
-
Constructs with no arg
-
before initialization
-
post construct
-
after initialization
-
pre destory
最后把代码贴出来,很简单,自己断点调试,看下控制台输出,会理解的更深!
public class Green {
@PostConstruct
public void init(){
System.out.println("post construct");
}
@PreDestroy
public void destroy(){
System.out.println("pre destory");
}
public Green(){
System.out.println("Constructs with no arg");
}
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("post process bean factory");
}
}
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before initialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after initialization");
return bean;
}
}
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Green.class);
context.register(MyBeanFactoryPostProcessor.class);
context.register(MyBeanPostProcessor.class);
context.refresh();
context.registerShutdownHook();
}
}
总结
本文针对spring bean生命周期专题,进行从bean定义到bean销毁流程的整体讲解。推荐结合spring的官方文档和代码断点调试,来彻底理解整个生命周期,以及我们能对其做怎样的扩展。希望这篇文档能对你有一点点帮助。
欢迎关注技术公众号:凯哥的Java技术活