FactoryBean 和它的兄弟SmartFactoryBean!

之前松哥写过一篇文章,跟小伙伴们介绍了我们在面试中非常常见的一道面试题:

在这篇文章中,松哥也和各位小伙伴演示了了 FactoryBean 的一些具体用法,但是关于 FactoryBean 的一些具体实践,这篇文章中没有讲,那么今天我就来和大家聊一聊这个话题,顺便再来说说 FactoryBean 的兄弟 SmartFactoryBean。

1. 使用差别

FactoryBean 的用法我就不再重复了,这里来看下 SmartFactoryBean。

FactoryBean 有很多实现类,但是继承自 FactoryBean 的接口却只有 SmartFactoryBean 一个。

SmartFactoryBean 接口的定义如下:

java 复制代码
public interface SmartFactoryBean<T> extends FactoryBean<T> {

	default boolean isPrototype() {
		return false;
	}

	default boolean isEagerInit() {
		return false;
	}

}

可以看到,SmartFactoryBean 就是在 FactoryBean 的基础之上多了两个方法:

  • isPrototype:这个方法就是返回当前 Bean 是否是多实例。初看这个方法,有的小伙伴可能会感觉到诧异,因为在 FactoryBean 中实际上有一个跟它功能类似的方法叫做 isSingleton,isSingleton 的意思就是说这个 Bean 是否是单例的,那么为什么现在还多了一个 isPrototype 方法呢?在前面的视频中松哥和大家讲过,Spring 中 Bean 的 scope 一共有六种,singleton 和 prototype 只是其中的两种,所以,isSingleton 为 true 就表示是单例,但是为 false 并不能表示就是 prototype,同理,isPrototype 为 true 就表示是多实例,但是 isPrototype 为 false 并不能表示就是 singleton,因此,这两个方法是不冲突的。
  • isEagerInit:这个方法就好理解了,方法名表示是否要提前初始化 Bean。当我们使用 FactoryBean 的时候,默认情况下,Spring 在初始化 Bean 的时候,初始化的是工厂 Bean,例如我们有一个 UserFactoryBean,那么默认情况下,Spring 容器初始化的是 UserFactoryBean,而 UserFactoryBean 中 getObject 方法真正要返回的 User 则在第一次使用的时候,才会被初始化,不知不觉中,目标 Bean 的初始化就被延迟了。如果不使用 SmartFactoryBean 的话,那我们得通过 Bean 的提前注入等方式去实现 Bean 的提前初始化,如果使用 SmartFactoryBean 的话,那么就可以通过配置 isEagerInit 方法返回 true 来实现目标 Bean 提前初始化了。

关于第二个方法 isEagerInit,我举个例子给大家演示一下。

假设我有一个 User 类,如下:

java 复制代码
public class User {
    public User() {
        System.out.println("User-init");
    }
}

然后又有一个 UserFactoryBean,如下:

java 复制代码
@Component
public class UserFactoryBean implements FactoryBean<User> {
    public UserFactoryBean() {
        System.out.println("UserFactoryBean-init");
    }

    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

最后扫描这个 Bean 并且启动容器:

java 复制代码
@Configuration
@ComponentScan
public class JavaConfig {
}
public class Demo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
    }
}

那么当我只初始化容器,不从容器中获取任何 Bean,控制台就会只打印 UserFactoryBean-init

这就说明只有 UserFactoryBean 被 Spring 容器初始化了,我们的 User 对象其实还没被初始化,User 对象要在第一次使用的时候,才会被初始化。

如果我们的 UserFactoryBean 实现的是 SmartFactoryBean 接口,那么就可以按照如下方式进行配置:

java 复制代码
@Component
public class UserFactoryBean implements SmartFactoryBean<User> {
    public UserFactoryBean() {
        System.out.println("UserFactoryBean-init");
    }

    @Override
    public boolean isEagerInit() {
        return true;
    }

    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

此时我们启动容器,但是却不获取任何 Bean,那么大家就会发现,User-init 也打印出来了,目标 Bean 也被初始化了。

2. 原理分析

接下来我们就从源码的角度来和大家简单梳理一下。

大家知道,容器的初始化是从 refresh 方法开始的,refresh 在初始化的过程中会调用到 finishBeanFactoryInitialization,而在 finishBeanFactoryInitialization 方法中则会调用到 beanFactory.preInstantiateSingletons() 方法,这个方法的作用就是去初始化那些不是延迟加载的 Bean。

所以,问题的核心就在 beanFactory.preInstantiateSingletons() 方法中,一起来看下。

DefaultListableBeanFactory#preInstantiateSingletons:

java 复制代码
@Override
public void preInstantiateSingletons() throws BeansException {
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			if (isFactoryBean(beanName)) {
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
					getBean(beanName);
				}
			}
			else {
				getBean(beanName);
			}
		}
	}
	for (String beanName : beanNames) {
		Object singletonInstance = getSingleton(beanName);
		if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
			StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
					.tag("beanName", beanName);
			smartSingleton.afterSingletonsInstantiated();
			smartInitialize.end();
		}
	}
}

这个方法的逻辑还是比较好理解。

首先 beanNames 中保存的就是所有 bean 名称,然后进行遍历。

遍历的时候根据 beanName 获取到 RootBeanDefinition,然后查看类是否不是抽象类、是否是单例以及是否不延迟初始化,如果满足条件,那么就开始初始化。

初始化的时候,首先判断当前 beanName 是否是一个 FactoryBean,大家注意,如果是 FactoryBean,则调用 getBean 方法去获取 Bean,但是调用的时候,在 beanName 的前面加上了 FACTORY_BEAN_PREFIX,这个其实就是 &,在前面的视频中松哥和大家讲过,加上 & 之后,这里获取到的就不是目标 Bean,而是这个 FactoryBean。这就是为什么在第一小节中和大家说,使用 FactoryBean 会导致目标 Bean 延迟加载,原因就在这里,因为初始化的时候给 beanName 加上了 & 前缀,所以初始化的就不是目标 Bean 了。

接下来还有一个判断,如果初始化出来的 Bean 是一个 SmartFactoryBean 对象,并且 isEagerInit 方法还返回 true,那么就再次调用 getBean 方法进行 Bean 的初始化,此时 Bean 的初始化传入的 beanName 就没有添加前缀了,那么初始化的就是目标 Bean 了,这也和我们第一小节中讲的结论相符。

初始化的时候,如果判断当前 bean 不是一个 FactoryBean,那么就直接调用 getBean 方法进行 Bean 的初始化。

最后还有一段逻辑,就是根据 beanName 获取到实例名称,如果这个实例是一个 SmartInitializingSingleton 类型的,那么就调用一下它的 afterSingletonsInstantiated 方法。

那么上面这段源码还涉及到两个地方,分别是 isFactoryBean 和 getBean。

2.1 isFactoryBean

这个方法就是根据 beanName 判断是否是一个 FactoryBean,如下:

java 复制代码
@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
	String beanName = transformedBeanName(name);
	Object beanInstance = getSingleton(beanName, false);
	if (beanInstance != null) {
		return (beanInstance instanceof FactoryBean);
	}
	// No singleton instance found -> check bean definition.
	if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory cbf) {
		// No bean definition found in this factory -> delegate to parent.
		return cbf.isFactoryBean(name);
	}
	return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
	Boolean result = mbd.isFactoryBean;
	if (result == null) {
		Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
		result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
		mbd.isFactoryBean = result;
	}
	return result;
}

这个里边,首先调用 transformedBeanName 方法对 beanName 进行一个预处理:

java 复制代码
protected String transformedBeanName(String name) {
	return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public static String transformedBeanName(String name) {
	if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
		return name;
	}
	return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
		do {
			beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
		}
		while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
		return beanName;
	});
}
public String canonicalName(String name) {
	String canonicalName = name;
	// Handle aliasing...
	String resolvedName;
	do {
		resolvedName = this.aliasMap.get(canonicalName);
		if (resolvedName != null) {
			canonicalName = resolvedName;
		}
	}
	while (resolvedName != null);
	return canonicalName;
}

大家看到,在静态的 transformedBeanName 方法中,首先判断 beanName 是否是以 & 开头,如果不是,则直接返回 beanName 即可。否则就通过一个 do{}while() 将 beanName 中的 & 都给删除掉,防止出现类似 &&&&&&&user 这种 beanName。

然后调用 canonicalName 方法获取到规范的 beanName,因为 bean 可能存在别名,如果使用的是别名,则将之在 canonicalName 方法中解析为规范的 beanName。

有了 beanName 之后,接下来调用 getSingleton 方法去一级缓存中查询这个 Bean 是否已经完成初始化了,如果已经完成,那么直接判断该 beanInstance 是否为 FactoryBean 即可,默认情况下,显然不会走这条线。

接下来继续判断当前 beanFactory 中是否存在该 beanName 的定义,如果不存在,且当前 beanFactory 是 ConfigurableBeanFactory,那么就去父容器中检查这个 beanName 对应的 bean 是否是 isFactoryBean。

最后实在不行,就调用另外一个重载的 isFactoryBean 方法去判断,这个重载的方法逻辑就比较简单了,从 BeanDefinition 中获取到 Bean 的类型,然后判断是否是 FactoryBean 即可。

这就是判断一个 beanName 对应的 Bean 是否为 FactoryBean 的所有逻辑。

2.2 getBean

另一方面就是 getBean 方法了,这个方法的执行可以分为两步。

再来回顾下 preInstantiateSingletons 方法中的如下逻辑:

java 复制代码
if (isFactoryBean(beanName)) {
	Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
	if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
		getBean(beanName);
	}
}

可以看到,无论是否提前初始化目标 Bean,都需要先初始化 FactoryBean,也就是自动加上 & 前缀然后去调用 getBean 方法,FactoryBean 的初始化就和普通 Bean 的初始化流程一样,我这里就不重复了。

然后,如果是要提前初始化 Bean,则还会再调用一次 getBean 方法,这次调用不加 & 前缀,所以这次调用最终就会触发到 FactoryBean 的 getObject 方法。

getBean 的调用最终会来到 AbstractBeanFactory#doGetBean 方法中,我们来简单看下这个方法的逻辑,这个方法比较长,我这里列出来跟我们相关的一部分:

java 复制代码
protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	String beanName = transformedBeanName(name);
	Object beanInstance;
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
    //省略。。。
}

小伙伴们看到,这里首先会调用 transformedBeanName 方法对 bean 名称进行处理,处理之后的 bean 名称就不带 & 了(2.1 小节已经介绍过),然后根据 bean 名称去单例池中获取 Bean 实例,如果是第一次来,也就是初始化 UserFactoryBean 的那一次,那次显然单例池中是没有东西的,那么那么就会进入到 Bean 的创建流程中,并在创建完成后,将 Bean 实例存入到单例池中(实际上存的是 UserFactoryBean 的实例)。

如果是第二次进来,由于上一次已经完成了 UserFactoryBean 的初始化了,第二次进来单例池中显然是有东西的,而且这个东西就是 UserFactoryBean 的实例,所以第二次进来之后,会进入到接下来的 if 分支中(第一次不会进入到该分支),在这个分支中,最终触发 getObject 方法的调用。

大家注意,getObjectForBeanInstance 方法传入了两个 bean 名称参数,第一个 name 是没有去除 & 的 beanName(可能包含 & 前缀),第二个参数则是经过处理的 beanName,即去除了 & 的 beanName。

来看下 getObjectForBeanInstance 方法:

java 复制代码
protected Object getObjectForBeanInstance(
		Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if (!(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
		}
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		return beanInstance;
	}
	if (!(beanInstance instanceof FactoryBean)) {
		return beanInstance;
	}
	Object object = null;
	if (mbd != null) {
		mbd.isFactoryBean = true;
	}
	else {
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

整体上来看,这里有四个 if 分支,我们分别来看。

第一个 if 分支主要是判断想要获取的 Bean 到底是不是一个 FactoryBean?BeanFactoryUtils.isFactoryDereference(name) 其实就是判断当前这个 name 是否以 & 开始,如果是以 & 开始,那就说明想要获取的就是 FactoryBean 实例,此时就检查 beanInstance 是否为 NullBean,是否为 FactoryBean,如果都检测没问题,那么就把 bean 直接返回即可。如果我们是想要从 Spring 容器中获取一个 FactoryBean 的实例,那么很明显就是走的这条线。

第二个 if 是检查 beanInstance 如果不是 FactoryBean 的实例,说明可能就是一个普通 Bean,那么就不需要额外处理,直接返回即可。

第三个 if 是标记 FactoryBean 的,这个没啥好说的。

第四个 if 分支则是将 beanInstance 转为 FactoryBean,然后合并一下 BeanDefinition,进而判断一下这个 Bean 是否是在内部使用(synthetic),最后调用 getObjectFromFactoryBean 方法去获取 Bean 对象,注意第三个参数是 !synthetic,这个参数表示这个类是否要使用 BeanPostProcessor 对其进行处理,只要这个 Bean 不是内部使用(synthetic=false),那么就会给其应用上 BeanPostProcessor。

继续来看 getObjectFromFactoryBean 方法:

java 复制代码
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
	if (factory.isSingleton() && containsSingleton(beanName)) {
		synchronized (getSingletonMutex()) {
			Object object = this.factoryBeanObjectCache.get(beanName);
			if (object == null) {
				object = doGetObjectFromFactoryBean(factory, beanName);
				Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
				if (alreadyThere != null) {
					object = alreadyThere;
				}
				else {
					if (shouldPostProcess) {
						if (isSingletonCurrentlyInCreation(beanName)) {
							return object;
						}
						beforeSingletonCreation(beanName);
						object = postProcessObjectFromFactoryBean(object, beanName);
						
					}
					if (containsSingleton(beanName)) {
						this.factoryBeanObjectCache.put(beanName, object);
					}
				}
			}
			return object;
		}
	}
	else {
		Object object = doGetObjectFromFactoryBean(factory, beanName);
		if (shouldPostProcess) {
			object = postProcessObjectFromFactoryBean(object, beanName);
		}
		return object;
	}
}

这里首先调用 factory.isSingleton() 方法去判断这个 Bean 是否是单例模式,该方法就是我们第一小节和大家分析的方法。

如果是单例模式,则去单例池 factoryBeanObjectCache 中获取到 Bean 并返回即可,当然,单例池中可能并不存在这个 Bean,那么就调用 doGetObjectFromFactoryBean 方法进行加载,加载成功之后,再给其应用上 BeanPostProcessor,最后还要将加载的结果存入到单例池 factoryBeanObjectCache 中,方便下一次使用。

如果不是单例模式,那么就不去单例池中查找,直接调用 doGetObjectFromFactoryBean 方法去获取 Bean 实例即可,获取到之后,也根据 shouldPostProcess 参数为之应用 BeanPostProcessor。

所有的线索都指向了 doGetObjectFromFactoryBean,我们再来看这个方法:

java 复制代码
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
	Object object;
		if (System.getSecurityManager() != null) {
			AccessControlContext acc = getAccessControlContext();
			object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
		}
		else {
			object = factory.getObject();
		}
	}
	if (object == null) {
		object = new NullBean();
	}
	return object;
}

这里做了一些权限的判断(防止对代码块没有执行权限),当然无论是否有权限,最终都会调用到 factory.getObject() 方法,终于到终点啦~

拿到 object 之后,再做一个简单判断,如果 object 为 null,那么就创建一个 NullBean 并返回即可。

好啦,这就是 FactoryBean 的完整创建流程啦~

3. 小结

好啦,今天就和小伙伴们分享了 FactoryBean 和它的兄弟 SmartFactoryBean,其实无论是目标 Bean 还是 FactoryBean,一开始的处理流程都是相似的,分歧产生在 AbstractBeanFactory#doGetBean 方法中,从这个方法中是否获取到 beanInstance 实例开始,一个向东一个向西~

小伙伴们不妨 debug 走一遍流程哦~

相关推荐
求知若饥10 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
禁默39 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework1 小时前
【jenkins插件】
java
风_流沙1 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes