【转载】Spring 源码分析之 bean 实例化原理
本次主要想写spring bean的实例化相关的内容。创建spring bean 实例是spring bean 生命周期的第一阶段。bean 的生命周期主要有如下几个步骤:
- 创建bean的实例
- 给实例化出来的bean填充属性
- 初始化bea
- 通过IOC容器使用bean
- 容器关闭时销毁bean
在实例化bean之前在BeanDefinition里头已经有了所有需要实例化时用到的元数据,接下来spring 只需要选择合适的实例化方法以及策略即可。实例化方法有两大类分别是工厂方法和构造方法实例化,后者是最常见的。spring默认的实例化方法就是无参构造函数实例化。
如我们在xml里定义的 <bean id="xxx" class="yyy"/> 以及用注解标识的bean都是通过默认实例化方法实例化的。
- 两种实例化方法(构造函数 和 工厂方法)
- 源码阅读
- 实例化策略(cglib or 反射)
两种实例化方
使用适当的实例化方法为指定的bean创建新实例:工厂方法,构造函数实例化。
代码演示
启动容器时会实例化所有注册的bean(lazy-init懒加载的bean除外),对于所有单例非懒加载的bean来说当从容器里获取bean(getBean(String name))的时候不会触发,实例化阶段,而是直接从缓存获取已准备好的bean,而生成bean的时机则是下面这行代码运行时触发的。
@Test
public void testBeanInstance(){
// 启动容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
}
一 使用工厂方法实例化(很少用)
1.静态工厂方法
public class FactoryInstance {
public FactoryInstance() {
System.out.println("instance by FactoryInstance");
}
}
public class MyBeanFactory {
public static FactoryInstance getInstanceStatic(){
return new FactoryInstance();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="factoryInstance" class="spring.service.instance.MyBeanFactory"
factory-method="getInstanceStatic"/>
</beans>
输出结果为:
instance by FactoryInstance
2.实例工厂方法
public class MyBeanFactory {
/**
* 实例工厂创建bean实例
*
* @return
*/
public FactoryInstance getInstance() {
return new FactoryInstance();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="myBeanFactory" class="MyBeanFactory"/>
<bean id="factoryInstance" factory-bean="myBeanFactory" factory-method="getInstance"/>
</beans>
输出结果为:
instance by FactoryInstance
二 使用构造函数实例化(无参构造函数 & 有参构造函数)
1.无参构造函数实例化(默认的)
public class ConstructorInstance {
public ConstructorInstance() {
System.out.println("ConstructorInstance none args");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="constructorInstance" class="spring.service.instance.ConstructorInstance"/>
</beans>
输出结果为:
ConstructorInstance none args
1.有参构造函数实例化
public class ConstructorInstance {
private String name;
public ConstructorInstance(String name) {
System.out.println("ConstructorInstance with args");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="constructorInstance" class="spring.service.instance.ConstructorInstance">
<constructor-arg index="0" name="name" value="test constructor with args"/>
</bean>
</beans>
输出结果为:
ConstructorInstance with args
源码阅读
下面这段是 有关spring bean生命周期的代码,也是我们本次要讨论的bean 实例化的入口。
doCreateBean方法具体实现在AbstractAutowireCapableBeanFactory类,感兴趣的朋友可以进去看看调用链。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
//第一步 创建bean实例 还未进行属性填充和各种特性的初始化
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
Object exposedObject = bean;
try {
// 第二步 进行属性填充
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 第三步 初始化bean 执行初始化方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}catch (Throwable ex) {
// 抛相应的异常
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
我们这里只需关注第一步创建bean实例的流程即可
instanceWrapper = createBeanInstance(beanName, mbd, args);
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// 使用工厂方法进行实例化
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Need to determine the constructor...
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 使用带参构造函数初始化
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 默认实例化方式 无参构造实例化
return instantiateBean(beanName, mbd);
}
上面代码就是spring 实现bean实例创建的核心代码。这一步主要根据BeanDefinition里的元数据定义决定使用哪种实例化方法,主要有下面三种:
- instantiateUsingFactoryMethod 工厂方法实例化的具体实现
- autowireConstructor 有参构造函数实例化的具体实现
- instantiateBean 默认实例化具体实现(无参构造函数)
实例化策略(cglib or 反射)
工厂方法的实例化手段没有选择策略直接用了发射实现的
实例化策略都是对于构造函数实例化而言的
上面说到的两构造函数实例化方法不管是哪一种都会选一个实例化策略进行,到底选哪一种策略也是根据BeanDefinition里的定义决定的。
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
上面这一行代码就是选择实例化策略的代码,进入到上面两种方法的实现之后发现都有这段代码。
下面选一个instantiateBean的实现来介绍
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
final BeanFactory parent = this;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
}, getAccessControlContext());
}
else {
// 在这里选择一种策略进行实例化
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}
选择使用反射还是cglib
先判断如果beanDefinition.getMethodOverrides()为空也就是用户没有使用replace或者lookup的配置方法,那么直接使用反射的方式,简单快捷,但是如果使用了这两个特性,在直接使用反射的方式创建实例就不妥了,因为需要将这两个配置提供的功能切入进去,所以就必须要使用动态代理的方式将包含两个特性所对应的逻辑的拦截增强器设置进去,这样才可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。---引用自《spring 源码深度剖析》这本书
<bean id="constructorInstance" class="spring.service.instance.ConstructorInstance" >
<lookup-method name="getName" bean="xxx"/>
<replaced-method name="getName" replacer="yyy"/>
</bean>
如果使用了lookup或者replaced的配置的话会使用cglib,否则直接使用反射。
具体lookup-method和replaced-method的用法可以查阅相关资料。
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (bd.getMethodOverrides().isEmpty()) {
constructorToUse = clazz.getDeclaredConstructor((Class[]) null);
return BeanUtils.instantiateClass(constructorToUse);
}else {
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
由于篇幅省略了部分代码
【转载】Spring 源码分析之 bean 实例化原理的更多相关文章
- Spring 源码分析之 bean 实例化原理
本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ...
- Spring 源码分析之 bean 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
- Spring Ioc源码分析系列--Bean实例化过程(一)
Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...
- Spring Ioc源码分析系列--Bean实例化过程(二)
Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Spring源码-IOC部分-Bean实例化过程【5】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- 【Spring源码分析】Bean加载流程概览(转)
转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring源码分析:Bean加载流程概览及配置文件读取
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...
随机推荐
- tarjam 模板改编
思路要灵活 邻接表涉及数组问题,可以用vector代替
- python学习笔记:sys、os模块
os模块:负责程序与操作系统的交互,提供了访问操作系统底层的接口; sys模块:负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境. --os 常用方法-- ...
- jmeter 不同线程组之间传递变量1
一 采用全局变量在不同线程组之间传递变量的坑 ${__setProperty(newcompanyId,${companyId},)}; 不采用全局变量传递参数,请求报文格式如下: 正确的报文: {& ...
- 程序性能优化之APK大小优化(六)下篇
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将继续从微信资源混淆AndResGuard原理来介绍AP ...
- mybatis 教程(mybatis in action)
目录简介: 一:开发环境搭建二:以接口的方式编程 三:实现数据的增删改查 四:实现关联数据的查询 五:与spring3集成(附源码) 六:与Spring MVC 的集成 七:实现mybatis分页(源 ...
- Redis事务 和 pipleline
1.reidis事务 Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证: 批量操作在发送 EXEC 命令前被放入队列缓存. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败 ...
- 【牛客网-剑指offer】变态跳台阶
题目: 一只青蛙一次可以跳上1级台阶,也可以跳上2级--它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 考点: 递归和循环 分析: 台阶数 跳法 1 1 2 2 3 4 4 8 5 1 ...
- css3两个汤圆亲吻动效
效果图: 模板来源:https://www.17sucai.com/pins/demo-show?id=35132 自己仿写出来的效果图: 笔记: 1.transform:translate(-50% ...
- 【记录】linux 常用命令梳理
命令梳理 下载文件 [wget 是一个十分常用命令行下载工具,多数Linux发行版本都默认包含这个工具] wget -c [zip路径等] #断点续传,如果下载中断,那么连接恢复时会从上次断点开始下载 ...
- shell脚本 set命令