Spring bean到底是如何创建的?(上)
前言
众所周知,spring对于java程序员来说是一个及其重要的后端框架,几乎所有的公司都会使用的框架,而且深受广大面试官的青睐。所以本文就以常见的一个面试题"spring bean的生命周期"为切入点,从源码的角度带领大家来看一看 spring bean到底是如何创建的 。spring bean的生命周期非常重要 ,因为几乎所有的跟spring整合的框架,比如说mybatis 、dubbo 等框架基本上都是通过bean的生命周期来实现跟spring的整合。
本文是基于spring源码的5.1版本
在讨论spring创建bean的源码之前,我先大概介绍一下spring的ioc和aop的概念。
ioc(Inversion of Control,缩写为IoC)就是控制翻转的意思,简单来说就是你按照spring提供的配置bean的方式将bean的创建流程交给spring来完成,比如以xml的方式声明bean,以@Bean的注解声明bean,以@Componet注解方式声明bean,当你用这些方式来声明bean的时候,spring在启动的时候就知道要为这个类创建一个对象,接下来spring会按照自己的流程来一步一步完成bean的生成过程,也就是本文的主题,spring bean的创建流程。
aop(Aspect Oriented Programming)英文意思就是面向切面编程,说白了其实就是一个动态代理的过程,只不过spring将生成动态代理的过程给封装到框架的内部,开发者其实只需要声明一下对哪个对象的哪个方法进行代理(pointcut)和在被代理的方法该执行什么样的代码(advice),这样就实现了aop。
有时大家可能会很好奇怎么实现动态代理的过程,怎么我配置了一下切面,就给我代理了呢?我给大家简单解释一下,aop的实现离不开ioc,当在spring bean创建的过程,在某个环节(后面会说到)spring框架会去判断,你有没有配置过给创建的对象进行代理,怎么判断很简单,就是根据你配置的切点表达式判断一下,如果有就给你创建一个代理对象返回给你,这样你拿到的就是代理对象,接下来你对这个对象方法调用就会走你写的那个advice所对应的代码,如果判断没有,就会返回给你原来的对象,就这么简单。
大家不妨去了解一下静态代理,这会有助于大家了解动态代理,动态代理其实跟静态代理差不多,只不过静态代理需要你手动写对象的代理,属于硬编码的方式,有多少个类就得写多少个类的代理类,很麻烦,而动态代理是动态生成代理类,但是本质都是加一些特殊的功能,这里就不再过多赘述了。
好了说完了spring ioc和aop的基本概念之后,接下来就来进入spring ioc中的bean的生命周期源码分析。
一、Spring Bean 元信息(BeanDefinition)读取阶段
spring容器刚启动的时候,spring会按照你的声明bean的方式(以xml的方式声明bean,以@Bean的注解声明bean,以@Componet注解方式声明bean等(其实也可以通过Properties资源配置))去读取你声明的信息,然后封装在一个叫BeanDefinition对象里面,BeanDefinition可以看成你配置信息的一个封装对象,就跟你平时new的User是一个概念,后面在创建对象的时候,spring拿出来BeanDefinition,基于你配置的信息来创建对象。
二、 Spring Bean 注册阶段
Spring Bean 元信息读取阶段结束后会为每一个bean生成一个BeanDefinition,你配置了那么多的bean就有那么多BeanDefinition吧,怎么都得有一个地方存吧,可以很好想到的,java中数据结构还是很多的,比如说list,map等,所以spring选择通过map来存,只不过spring考虑的比较完善,封装了一个类,叫BeanDefinitionRegistry,直接实现就是DefaultListableBeanFactory这个类(这个类是spring ioc核心类,是BeanFactory基本上算是唯一的实现,非常非常的重要),通过registerBeanDefinition方法进行注册
在此为止,你通过xml或者各种方式声明的bean已经注册到ioc容器中了,所谓的注册,就是表明,这些对象需要spring来帮你创建,虽然你只配置了一下,但是spring得去做很多的事来读取配置。所以接下来就是通过spring 来获取到你注册bean,就会进入spring bean的创建阶段
三、Bean的获取阶段
为什么先讲获取。因为spring源码中是先从ioc容器中获取对象,获取不到再创建的。所以这里先从DefaultListableBeanFactory的doGetBean方法入手,它会委派给它的父类来处理
先根据getSingleton方法去获取对象 ,这里就牵扯出三级缓存解决循环依赖的问题.。
spring是如何解决循环依赖的?
我之前写过一篇关于三级缓存是如何解决循环依赖的,文章通过画图的方式,一步一步来讲解三级缓存在Bean创建过程中是如何发挥作用的,以及三级缓存不能解决循环依赖的场景。如果有需要的话可以关注微信公众号 三友的java日记 ,回复 循环依赖 即可获取文章链接。

四、Bean的实例化阶段
从这个阶段开始,bean就会一步一步被创建出来父容器也没有,就要自己去创建这个对象,在创建之前合并BeanDefinition 和 注册依赖的bean,@DependsOn注解就是在这个阶段发挥作用的
接下来就是对bean的作用返回进行判断,从这里可以看出,其实spring对于bean的作用范围中的单例和多例其实是采用硬编码的方式来进行完成的,其余的bean的作用范围,比如在web环境中的bean作用域session、springcloud环境中的@RefreshScope注解等都是通过扩展org.springframework.beans.factory.config.Scope的实现来完成的。大家有兴趣可以看看SessionScope和RefreshScope这两个实现类。
补充一点,肯定会有人好奇,我的代码明明没有动过,我的Controller一直在那,怎么做到的一个session一个Controller,其实原理很简单,就是你看见的Controller其实是个代理对象,每次调用的时候都会根据session的不同去重新创建一个新的真正的Controller对象去调用,这里涉及到spring aop的知识,有机会我们再讲。不过从这里可以看出,spring 的 ioc和aop是spring的核心功能,spring实现的其他机制,很多都是通过这两个特性展开的。
接着我们顺着创建单例bean继续往下看,把创建单例bean的重要的每个环节都看一遍,从这我们就开始深入bean的生命周期源码阶段。从createBean方法开始
1)bean class 的加载阶段
因为可能是通过xml文件来声明bean的,所以要把bean class加载一下
2)bean实例化之前阶段
这个阶段主要是回调所有的InstantiationAwareBeanPostProcessor对象的postProcessBeforeInstantiation方法,这个阶段如果有返回对象,直接不走下面的生命周期了(因为返回值不为null,直接return了),所以一般没有人这么玩。
BeanPostProcessor组件体系
InstantiationAwareBeanPostProcessor,这个接口是BeanPostProcessor的子类,BeanPostProcessor接口及其衍生的接口(接下来我称为BeanPostProcessor组件)是bean生命周期中一个非常核心的类体系,因为spring bean在创建过程中不同的阶段都会回调BeanPostProcessor组件的方法,这样就可以达到扩展的目的。因为只要你自己实现了BeanPostProcessor组件,就可以在生命周期的不同阶段可以对你的bean进行不同的操作,达到自己的目的。比如说阿里开源的dubbo中@DubboReference注解(2.7.7版本推出的注解,取代@Reference注解,功能没有什么变化)在整合spring的过程中主要是通过ReferenceAnnotationBeanPostProcessor来实现的,这个接口就是BeanPostProcessor的实现。说实话,bean的生命周期一大部分都是通过BeanPostProcessor组件来完成扩展的。
我们继续往下看
进入resolveBeforeInstantiation方法
进入applyBeanPostProcessorsBeforeInstantiation方法
3)bean的实例化阶段
这个阶段是根据你的class对象,来创建一个实例对象出来。
进入doCreateBean方法
进入createBeanInstance方法,对象就在这个方法创建的
@Bean的构建方式、构造器注入创建对象的方式,这两个创建的细节就不研究了
通过带构造参数的实例化构造方法来实例化我们就不看了。那么就进入instantiateBean方法
从这里可以看出,不论怎么走,都是通过getInstantiationStrategy方法获取实例化对象的策略然后调用instantiate来实例化对象。点进getInstantiationStrategy方法会发现其实是获取的CglibSubclassingInstantiationStrategy,那么我们就进入instantiate方法
这里我们可以看出,其实是获得了class的默认构造器,然后调用BeanUtils.instantiateClass(constructorToUse)来实例化对象,这是这内部就是简单的反射调用构造器创建对象。就不点进去了。
其实从这里我们可以看出,其实spring在创建对象实例的时候,最简单的方式其实就是通过反射直接通过调用的构造方法进行实例化。其实spring对象的实例化还有其他的方式,比如我上面图片标注的@Bean的构建方式、构造器注入创建对象的方式都不是走这。
在后面就是对创建创建出来的对象包装成BeanWrapper对象,直接返回。至此,bean的对象就被实例化出来了。
4) bean 的实例化之后阶段
接着往下看。
这是一个很重要的一步,主要是为了解决循环依赖的,跟文章最前面说的解决循环依赖是能够相呼应上的。
接下来看populateBean方法
看看,这里就是继续回调BeanPostProcessor组件体系的方法,所以回调完就表明spring bean的创建阶段完成。至于这个阶段为什么叫spring的bean的实例化之后阶段,你可以看看回调的方法的名字,翻译过来就是后置处理在bean实例化之后,所以叫这个阶段。
这个方法回调完之后下面代码就是bean生命周期中又一个核心的阶段,那就是属性赋值阶段,什么@Autowired依赖注入之类的其实就是在下面代码给你完成的。但是你有没有发现,postProcessAfterInstantiation如果这个方法返回false,下面的代码就不会执行了,所以一般扩展也没有返回false的,没人这么玩。其实你可以发现,spring在bean的创建过程中提供了非常多的可扩展点,你可以在每个阶段改变bean的创建行为,虽然可能没有人去这么做,但是spring依然提供了这些点。其实这也是读源码的有趣的地方,你可以看见各种扩展点,自己就可以去使用扩展点,进行各种骚操作。
总结
我们把这篇文章总结一下,最开始根据配置bean的方式封装到BeanDefinition中注册到BeanDefinitionRegistry中,然后说讲了bean的获取,自己的容器获取不到就会从父容器获取,如果都没获取到就会自己创建。说创建之前,简单的说明了spring是如何通过三级缓存解决循环依赖的问题。创建的时候会根据bean的作用域不同,进行了不同的创建。接下来我们选择了深入单例bean的创建源码,进入了bean创建的生命周期创建阶段,bean class 的加载,bean的实例化阶段,详细分为实例化之前阶段、实例化阶段、实例化之后阶段,顺便插入了对BeanPostProcessor组件体系的讲解。至于spring bean的生命周期的其它阶段,比如属性赋值阶段,可以看一下这篇文章Spring Bean到底是如何创建的(下)。
如果觉得这篇文章对你有所帮助,还请帮忙点赞、在看、转发给更多的人,码字不易,非常感谢!
往期热门文章推荐
扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习。
Spring bean到底是如何创建的?(上)的更多相关文章
- spring bean是什么
Spring有跟多概念,其中最基本的一个就是bean,那到底spring bean是什么? Bean是Spring框架中最核心的两个概念之一(另一个是面向切面编程AOP). 是否正确理解 Bean 对 ...
- Spring读书笔记——bean创建(上)
通过<Spring读书笔记--bean加载>和<Spring读书笔记--bean解析>,我们明白了两件事. Spring如何加载消化一个xml配置文件 Spring如何将xml ...
- Spring IoC bean 的创建(上)
前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 本篇文章主要介绍 Spring IoC 容 ...
- Spring bean 创建过程源码解析
在上一篇文件 Spring 中 bean 注册的源码解析 中分析了 Spring 中 bean 的注册过程,就是把配置文件中配置的 bean 的信息加载到内存中,以 BeanDefinition 对象 ...
- Spring 源码(10)Spring Bean 的创建过程(1)
Spring Bean的创建刚开始进行了一些准备工作,比如转换服务的初始化,占位符解析器的初始化,BeanDefinition元数据的冻结等操作,都是为了在创建Bean的过程中保证Bean的正确的创建 ...
- Spring 源码(11)Spring Bean 的创建过程(2)
Spring Bean 的创建过程介绍了FactoryBean 的创建方式,那么接下来介绍不是FactoryBean的创建方式,在创建过程中,又会分为单例的Bean的创建,原型类型的Bean的创建等. ...
- Spring 源码(12)Spring Bean 的创建过程(3)
继续上一篇Spring Bean的创建过程的解读,上一篇介绍了Spring在创建过程中doGetBean方法,在执行过程中会调用getSingleton方法并且设置一个lambda表达式,这个lamb ...
- Spring 源码(13)Spring Bean 的创建过程(4)
Spring Bean的创建过程非常的复杂,上一篇重点介绍了Spring在创建Bean的过程中,使用InstantiationBeanPostProcessor进行提前创建Bean,我们可以通过CGL ...
- 【spring bean】bean的配置和创建方式
---恢复内容开始--- 项目结构如下: lib如下: 1.首先建立SayHell.java接口 package com.it.sxd; public interface SayHell { publ ...
随机推荐
- 【.NET 6】使用EF Core 访问Oracle+Mysql+PostgreSQL并进行简单增改操作与性能比较
前言 唠嗑一下.都在说去O或者开源,但是对于数据库选型来说,很多人却存在着误区.例如,去O,狭义上讲,是去Oracle数据库.但是从广义上来说,是去Oracle公司产品或者具有漂亮国垄断地位和需要商 ...
- Vue路由跳转时修改页面标题
1 在main.js中添加如下代码 import Vue from 'vue' import App from './App.vue' import router from './router' // ...
- 帝国CMS实现栏目批量修改:是否生成
帝国cms实现栏目批量修改 是否生成:不生成栏目页, 不生成内容页, 不生成JS调用, 标签不调用的修改方本文关键字词:栏目批量设置第一步./e/admin/SetMoreClass.php中查找: ...
- Vue基础二之全局API、实例属性和全局配置,以及组件进阶(mixins)的详细教程(案列实现,详细图解,附源码)
本篇文章主要是写Vue.directive().Vue.use()等常用全局API的使用,vm.$props.vm.$options.vm.$slots等实例属性的使用,以及Vue全局配置.组件的mi ...
- css 实现球里装水
<template> <div class="container"> <div class="wave"></ ...
- 【Hadoop】HBase组件配置
目录 HBase实验步骤: 1.配置时间同步(所有节点) 2.部署HBase(master节点) 3.配置HBase(master节点) 4.拷贝文件到slave节点 5.修改权限,切换用户(所有节点 ...
- 关于LVS中默认的DR模型图
虽然常用,但是关于DR模型最大的一个缺点就是Director和RS必须在同一个网络中,通过交换机连接,中间不能有路由
- 6.2 计划任务crond
创建.编辑计划任务的命令为crontab -e,查看当前计划任务的命令为crontab -l,删除某条计划任务的命令为crontab -r. 参数 作用 -e 编辑计划任务 -u 指定用户名称 -l ...
- HashMap源码理解一下?
HashMap 是一个散列桶(本质是数组+链表),散列桶就是数据结构里面的散列表,每个数组元素是一个Node节点,该节点又链接着多个节点形成一个链表,故一个数组元素 = 一个链表,利用了数组线性查找和 ...
- python学习-Day22
目录 今日内容详细 hashlib加密模块 什么是加密 加密算法 加密的使用 基本使用 指定算法(md5) 将明文数据传递给算法对象 获取加密之后的密文数据 加密补充 加盐处理 动态加盐 加密应用场景 ...
