Bean的加载过程

spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典、优秀的框架,它的复杂程度往往令人望而却步。

不过作为朝夕相处的框架,我们必须得明白一个问题就是spring是如何加载bean的,我们常在开发中使用的注解比如@Component、@AutoWired、@Socpe等注解,Spring是如何解析的,明白这些原理将有助于我们更深刻的理解spring。

需要说明一点的是spring的源码非常精密、复杂,限于篇幅的关系,本篇博客不会细致的分析源码,会采取抽丝剥茧的方式,避轻就重,抓住重点来分析整个流程(不会分析具体的细节),本次将会基于spring5.0的版本

本文的目录:

  • spring读取配置或注解的过程
  • spring的bean的生命周期
  • spring的BeanPostProcessor处理器
  • 一些关键性的问题
  • 测试
  • 总结

一、spring读取配置或注解的过程

1、先通过扫描指定包路径下的spring注解,比如@Component、@Service、@Lazy @Sope等spring识别的注解或者是xml配置的属性(通过读取流,解析成Document,Document)然后spring会解析这些属性,将这些属性封装到BeanDefintaion这个接口的实现类中.

在springboot中,我们也可以采用注解配置的方式:


比如这个配置Bean,spring也会将className、scope、lazy等这些属性装配到PersonAction对应的BeanDefintaion中.具体采用的是BeanDefinitionParser接口中的parse(Element element, ParserContext parserContext)方法,该接口有很多不同的实现类。

通过实现类去解析注解或者xml然后放到BeanDefination中,BeanDefintaion的作用是集成了我们的配置对象中的各种属性,重要的有这个bean的ClassName,还有是否是Singleton、对象的属性和值等(如果是单例的话,后面会将这个单例对象放入到spring的单例池中)。spring后期如果需要这些属性就会直接从它中获取。

然后,再注册到一个ConcurrentHashMap中,在spring中具体的方法就是registerBeanDefinition(),这个Map存的key是对象的名字,比如Person这个对象,它的名字就是person,值是BeanDefination,它位于DefaultListableBeanFactory类下面的beanDefinitionMap类属性中,同时将所有的bean的名字放入到beanDefinitionNames这个list中,目的就是方便取beanName;

二、spring的bean的生命周期

spring的bean生命周期其实最核心的分为4个步骤,只要理清三个关键的步骤,其他的只是在这三个细节中添加不同的细节实现,也就是spring的bean生明周期:

实例化和初始化的区别:实例化是在jvm的堆中创建了这个对象实例,此时它只是一个空的对象,所有的属性为null。而初始化的过程就是讲对象依赖的一些属性进行赋值之后,调用某些方法来开启一些默认加载。比如spring中配置的数据库属性Bean,在初始化的时候就会将这些属性填充,比如driver、jdbcurl等,然后初始化连接

2.1、实例化 Instantiation

AbstractAutowireCapableBeanFactory.doCreateBean中会调用createBeanInstance()方法,该阶段主要是从beanDefinitionMap循环读取bean,获取它的属性,然后利用反射(core包下有ReflectionUtil会先强行将构造方法setAccessible(true))读取对象的构造方法(spring会自动判断是否是有参数还是无参数,以及构造方法中的参数是否可用),然后再去创建实例(newInstance)

2.2、初始化

初始化主要包括两个步骤,一个是属性填充,另一个就是具体的初始化过程

2.2.1、属性赋值

PopulateBean()会对bean的依赖属性进行填充,@AutoWired注解注入的属性就发生这个阶段,假如我们的bean有很多依赖的对象,那么spring会依次调用这些依赖的对象进行实例化,注意这里可能会有循环依赖的问题。后面我们会讲到spring是如何解决循环依赖的问题

2.2.2、初始化 Initialization

初始化的过程包括将初始化好的bean放入到spring的缓存中、填充我们预设的属性进一步做后置处理等

3: 使用和销毁 Destruction

在Spring将所有的bean都初始化好之后,我们的业务系统就可以调用了。而销毁主要的操作是销毁bean,主要是伴随着spring容器的关闭,此时会将spring的bean移除容器之中。此后spring的生命周期到这一步彻底结束,不再接受spring的管理和约束。

三、spring的BeanPostProcessor处理器

spring的另一个强大之处就是允许开发者自定义扩展bean的初始化过程,最主要的实现思路就是通过BeanPostProcessor来实现的,spring有各种前置和后置处理器,这些处理器渗透在bean创建的前前后后,穿插在spring生命周期的各个阶段,每一步都会影响着spring的bean加载过程。接下来我们就来分析具体的过程:

图片

3.1、实例化阶段**

该阶段会调用对象的空构造方法进行对象的实例化,在进行实例化之后,会调用InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法

BeanPostProcessor(具体实现是InstantiationAwareBeanPostProcessor). postProcessBeforeInstantiation();

这个阶段允许在Bena进行实例化之前,允许开发者自定义逻辑,如返回一个代理对象。不过需要注意的是假如在这个阶段返回了一个不为null的实例,spring就会中断后续的过程。

BeanPostProcessor.postProcessAfterInstantiation();

这个阶段是Bean实例化完毕后执行的后处理操作,所有在初始化逻辑、装配逻辑之前执行

3.2、初始化阶段

3.2.1、BeanPostProcessor.postProcessBeforeInitialization

该方法在bean初始化方法前被调用,Spring AOP的底层处理也是通过实现BeanPostProcessor来执行代理逻辑的

3.2.2、InitializingBean.afterPropertiesSet

自定义属性值 该方法允许我们进行对对象中的属性进行设置,假如在某些业务中,一个对象的某些属性为null,但是不能显示为null,比如显示0或者其他的固定数值,我们就可以在这个方法实现中将null值转换为特定的值

3.2.3、BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName)。

可以在这个方法中进行bean的实例化之后的处理,比如我们的自定义注解,对依赖对象的版本控制自动路由切换。比如有一个服务依赖了两种版本的实现,我们如何实现自动切换呢?

这时候可以自定义一个路由注解,假如叫@RouteAnnotaion,然后实现BeanPostProcessor接口,在其中通过反射拿到自定义的注解@RouteAnnotaion再进行路由规则的设定。

3.2.4、SmartInitializingSingleton.afterSingletonsInstantiated

4.1、容器启动运行阶段

4.1.1、SmartLifecycle.start

容器正式渲染完毕,开始启动阶段,bean已经在spring容器的管理下,程序可以随时调用

5.1、容器停止销毁

**5.1.1、SmartLifecycle.stop(Runnable callback) **

spring容器停止运行

5.1.2、DisposableBean.destroy()

spring会将所有的bean销毁,实现的bean实例被销毁的时候释放资源被调用

四、一些关键性的问题

4.1:FactoryBean和BeanFactory的区别?

BeanFactory是个bean 工厂类接口,是负责生产和管理bean的工厂,是IOC容器最底层和基础的接口,spring用它来管理和装配普通bean的IOC容器,它有多种实现,比如AnnotationConfigApplicationContext、XmlWebApplicationContext等。

FactoryBean是FactoryBean属于spring的一个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理,生产的对象是由getObject()方法决定的。

注意:它是泛型的,只能固定生产某一类对象,而不像BeanFactory那样可以生产多种类型的Bean。在对于某些特殊的Bean的处理中,比如Bean本身就是一个工厂,那么在其进行单独的实例化操作逻辑中,可能我们并不想走spring的那一套逻辑,此时就可以实现FactoryBean接口自己控制逻辑。

4.2、spring如何解决循环依赖问题

循环依赖问题就是A->B->A,spring在创建A的时候,发现需要依赖B,因为去创建B实例,发现B又依赖于A,又去创建A,因为形成一个闭环,无法停止下来就可能会导致cpu计算飙升

如何解决这个问题呢?spring解决这个问题主要靠巧妙的三层缓存,所谓的缓存主要是指这三个map,singletonObjects主要存放的是单例对象,属于第一级缓存;singletonFactories属于单例工厂对象,属于第三级缓存;

earlySingletonObjects属于第二级缓存,如何理解early这个标识呢?它表示只是经过了实例化尚未初始化的对象。Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。

如果获取到了则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects,其实就是将三级缓存提升到二级缓存,这个就是缓存升级。spring在进行对象创建的时候,会依次从一级、二级、三级缓存中寻找对象,如果找到直接返回。

由于是初次创建,只能从第三级缓存中找到(实例化阶段放入进去的),创建完实例,然后将缓存放到第一级缓存中。下次循环依赖的再直接从一级缓存中就可以拿到实例对象了。

五、测试

我们来写一个测试类,验证一下上面的问题:

5.1、首先声明一个自定义的Bean

@Component
public class CustomBean {
    public CustomBean(){
        System.out.println("调用CustomBean空的构造方法");
    }
}

5.2、声明一个Bean来实现BeanPostProcessor

package com.wyq.spring.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.beans.PropertyDescriptor;

@Component
@Scope("singleton")
public class TestBean implements BeanPostProcessor, SmartInitializingSingleton, InstantiationAwareBeanPostProcessor, DisposableBean{

    private static final String BEAN_NAME= "customBean";

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>BeanPostProcessor.postProcessBeforeInitialization");
        }
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>BeanPostProcessor.postProcessAfterInitialization");
        }
        return null;
    }

    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("==>SmartInitializingSingleton.afterSingletonsInstantiated");

    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation");
        }
        return false;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessPropertyValues");
        return null;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("==>DisposableBean.destroy");
    }
}

5.3、启动容器:

六:总结

本篇文章主要是介绍了Spring的一些实例化的过程,高屋建瓴的分析了一下spring的bean加载过程,没有详细展开某个细节分析。spring的内部源码非常复杂,每个接口的实现类都在5个以上,如果深入细节,恐怕不是一篇博客能讲清楚的。

这篇文章的目的就是在阐述spring的基本脉络中心路线顺序,首先我们需要有一个总体的认识,然后再深入到细节就是轻而易举的了。

这也是一种学习的方法论,通过本篇文章我希望能梳理清楚spring的基本流程,对spring有一个比较清晰的认识。并且学习到优秀开源框架的设计基本思想,还有就是进一步提升自己的阅读源码的能力。

引用链接:Bean的加载过程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容