Spring 源码(11)Spring Bean 的创建过程(2)
Spring Bean 的创建过程介绍了FactoryBean 的创建方式,那么接下来介绍不是FactoryBean的创建方式,在创建过程中,又会分为单例的Bean的创建,原型类型的Bean的创建等。一般来说在Spring中几乎所有对象都是单例创建的,除非有其他业务需要设置为其他作用域的Bean,所以重点以创建单例Bean为例。
单例Bean的创建
在创建时会调用getBean,然后doGetBean,一般来说在Spring中只要是do开头方法基本就是真正干活的方法,所以我们看doGetBean方法的源码:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 解析成规范的Bean name ,因为可能是FactoryBean加了& 前缀的Bean或者是有别名的Bean
String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
// 获取缓存中的Bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// 如果缓存中没有,那么就会按照单例或者多例的方式创建
else {
// 省略代码....
// Check if bean definition exists in this factory.
// 检查父类容器
// 省略代码....
if (!typeCheckOnly) {
// 标记已经被创建
markBeanAsCreated(beanName);
}
try {
// 合并BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
// 判断是否存在依赖的Bean的创建,比如dependsOn 依赖 A 这个Bean,那么就需要先创建A这个bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
// 省略代码....
}
// 注册依赖的Bean,放在集合中
registerDependentBean(dep, beanName);
try {
// 创建Bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
// 省略代码....
}
}
}
// Create bean instance.
if (mbd.isSingleton()) {
// 如果是单例的,就去创建Bean
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建Bean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 省略代码....
}
});
// 获取bean对象,会进行检查获取对象是否是FactoryBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 原型作用的创建方式
else if (mbd.isPrototype()) {
// 省略代码....
}
else {
// 省略代码....
}
}
catch (BeansException ex) {
// 省略代码....
}
}
// 省略代码....
return (T) bean;
}
去掉不重要的代码,可以看到首先是从缓存中获取,如果没有获取到就进行一些列检查,最终检查是否单例的Bean,如果是,那么就会调用getSingleton方法,传入一个beanName,一个ObjectFactory的lambda表达式,表达式中有个createBean方法,这个方法就是创建的Bean方法。
那什么时候调用crateBean方法呢?
答案是执行lambda表达式的具体方法时执行,我们先看看这个ObjectFactory接口是啥?
ObjectFactory 对象工厂
直接看源码:
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
这个接口是一个函数式接口,可以用于lambda表达式直接使用,在调用getObject方法时就是真正执行lambda表达式中的方法。
具体看看getSingleton方法的源码:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
// 省略代码....
}
// 省略代码....
// 检查 并添加正在创建的单例对象到集合中
beforeSingletonCreation(beanName);
// 设置为新的单例对象标识
boolean newSingleton = false;
// 设置异常集合,出现异常时将异常加入到集合中
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 执行具体的方法,调用crateBean方法
singletonObject = singletonFactory.getObject();
// 标识为新的单例对象
newSingleton = true;
}
catch (IllegalStateException ex) {
// 省略代码....
}
catch (BeanCreationException ex) {
// 省略代码....
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 检查 并移除正在创建的单例对象
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 加入到缓存中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
这里执行完之后就会执行到lambda表达式中的createBean方法:
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 省略代码....
RootBeanDefinition mbdToUse = mbd;
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
// 解析Bean的Class 用于反射创建对象
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
try {
// 方法覆盖准备 lookup-method replace-method
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
// 省略代码....
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 解析提前实例化,使用InstantiationAwareBeanPostProcessor实现
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
// 省略代码....
}
try {
// 实例化 + 初始化 Bean
// 真正的创建Bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
// 省略代码....
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// 省略代码....
}
catch (Throwable ex) {
// 省略代码....
}
}
首先是进行了Bean的类型的解析,主要是用于后面的反射创建对象时使用,并设置到RootBeanDefinition中,然后进行方法覆盖操作。
Spring方法覆盖实战
方法覆盖就是使用了lookup-method和replace-method 标签的时候,就会进行方法的覆盖。方法覆盖有什么用处呢?
一般来说方法覆盖就是解决单例对象引用多例对象的时候使用方法覆盖。
做个实验试试:
定义一个房子类,用于停车
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public abstract class MyHouse {
public abstract MyCar park();
}
定义我的车
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public interface MyCar {
/**
* 买一辆车
* @return 车
*/
MyCar buy();
}
定义实现类:
宝马车:
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class BMW implements MyCar{
@Override
public MyCar buy() {
return this;
}
}
奔驰车:
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class Ben implements MyCar{
@Override
public MyCar buy() {
return this;
}
}
xml配置:
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myHouse1" class="com.redwinter.test.methodoverride.lookupmethod.MyHouse" >
<lookup-method name="park" bean="bmw"/>
</bean>
<bean id="myHouse2" class="com.redwinter.test.methodoverride.lookupmethod.MyHouse" >
<lookup-method name="park" bean="ben"/>
</bean>
<!-- 设置为原型-->
<bean id="bmw" class="com.redwinter.test.methodoverride.lookupmethod.BMW" scope="prototype"/>
<bean id="ben" class="com.redwinter.test.methodoverride.lookupmethod.Ben"/>
</beans>
测试类:
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class LookupTest {
/**
* lookup-method 用来解决单例对象多例对象的
*/
@Test
public void lookupTest(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:method-override.xml");
MyHouse myHouse = (MyHouse) context.getBean("myHouse1");
MyHouse myHouse1 = (MyHouse) context.getBean("myHouse1");
System.out.println(myHouse.park());
System.out.println(myHouse1.park());
}
}
输出:
com.redwinter.test.methodoverride.lookupmethod.BMW@4a765
com.redwinter.test.methodoverride.lookupmethod.BMW@3e6358
这里Myhouse是一个单例的对象,myHouse1 调用的方法每次调用都是不同的对象。
Spring是如何实现方法覆盖的?
源码过于繁琐和复杂,这里直接看执行流程:

Spring在加载BeanDefinition的时候,执行 parseLookupOverrideSubElements 这个方法的时候只要设置了lookup-method标签就会创建一个LookupOverride类放入到BeanDefinition 的 MethodOverrides 属性中,在进行Bean的创建的时候,就会判断这个属性值是否有值,如果有那么就会在对象实例化时获取一个实例化策略,然后执行实例化,就、就会调用SimpleInstantiationStrategy#instantiate 方法,然后使用CGLIB进行实例化,创建出一个Enhancer 增强类,并且设置一个回调类型为:
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
最终在执行方法的时候就会调用到回调类LookupOverrideMethodInterceptor 拦截器上,然后执行Bean的创建:
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Assert.state(lo != null, "LookupOverride not found");
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
if (StringUtils.hasText(lo.getBeanName())) {
// 创建Bean
Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
this.owner.getBean(lo.getBeanName()));
// Detect package-protected NullBean instance through equals(null) check
return (bean.equals(null) ? null : bean);
}
else {
return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
this.owner.getBean(method.getReturnType()));
}
}
这篇文章就介绍到这里,下一篇继续。
Spring 源码(11)Spring Bean 的创建过程(2)的更多相关文章
- 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 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
- dubbo源码分析3-service bean的创建与发布
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- Spring源码分析专题 —— IOC容器启动过程(上篇)
声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...
- 【Spring源码分析】Bean加载流程概览(转)
转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...
- Spring源码分析:Bean加载流程概览及配置文件读取
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring 源码分析之 bean 实例化原理
本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ...
- 初探Spring源码之Spring Bean的生命周期
写在前面的话: 学无止境,写博客纯粹是一种乐趣而已,把自己理解的东西分享出去,不意味全是对的,欢迎指正! Spring 容器初始化过程做了什么? AnnotationConfigApplication ...
随机推荐
- mysql常见命令参数(一)
1.mysql命令常用参数 1.--auto-rehash (tab键自动补全,表名及表字段) # mysql -u root --auto-rehash # vim my.cnf [mysql] a ...
- elasticsearch 5.6.7在线安装ik分词,亲测有效
官网的在线安装命令 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/rele ...
- (stm32f103学习总结)—printf重定向
一.printf重定向简介 我们知道C语言中printf函数默认输出设备是显示器,如果要实现在 串口或者LCD上显示,必须重定义标准库函数里调用的与输出设备相关的函数.比如使用printf输出到串口, ...
- 小程序输入框闪烁BUG解决方案
前言 本人所说的小程序,都是基于mpvue框架而上的,因此BUG可能是原生小程序的,也有可能是mpvue的. 问题描述 在小程序input组件中,如果使用v-model进行双向绑定,在输入时会出现光标 ...
- 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
概述 最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户单独选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图). 如今WijmoJS已经实现了该控件 ...
- Water 2.5.8 发布,一站式服务治理平台
Water(水孕育万物...) Water 为项目开发.服务治理,提供一站式解决方案(可以理解为微服务架构支持套件).基于 Solon 框架开发,并支持完整的 Solon Cloud 规范:已在生产环 ...
- JavaScript脚本延迟加载的方式有哪些?
延迟加载就是等页面加载完成之后再加载 JavaScript 文件. js 延迟加载有助于提高页面加载速度. 一般有以下几种方式: defer 属性: 给 js 脚本添加 defer 属性,这个属性会让 ...
- VsCode 常用插件清单
插件离线安装说明 在一些内网开发环境中,无法做到在线安装,这个时候就需要对插件进行离线安装 了 打开 VSCode 插件市场网址 Extensions for the Visual Studio fa ...
- FFMPEG第一次学习
习惯用OneNote笔记,直接复制了,链接和一些命令放在最下面了 里面的库文件是我下的雷神的课件文件,我传到了自己github,链接也放最后了 转载自:https://blog.csdn.net/le ...
- jni有关知识点总结
一.c/c++语言: 1.关于动态链接库的搜索问题: LIBRARY_PATH is used by gcc before compilation to search for directories ...