七、Spring 源码解析之 bean 的加载流程二:从缓存中获取单例 bean

  在六、Spring 源码解析之 bean 的加载过程介绍中我们介绍了bean 的加载的大致流程,其中第一步已经在该文中说过,这里来说第二步:尝试从缓存中加载单例
  说道从缓存中加载单例,就不得不提到 FactoryBean 的使用:

一、FactoryBean 的使用

一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 。在某些情况下,实例化 bean 过程就比较复杂,如果按照传统的方式,则需要在 <bean> 中提供大量的配置信息,配置方式的灵活性是受限得到,这是采用编码的方式可能会得到一个简单打方案。Spring 为此提供了一个 org.springframework.beans.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑

public interface FactoryBean<T> {

	public static final String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
	/**
	 * 返回由 FactoryBean 创建的 bean 实例,如果是 isSingleton() 返回 true,则该实例会放到 Spring 容器中单实例缓存池中
	 */
	@Nullable
	T getObject() throws Exception;

	/**
	 * 返回由 FactoryBean 创建的 bean 类型
	 */
	@Nullable
	Class<?> getObjectType();

	/**
	 * 返回由 FactoryBean 创建的 bean 实例的作用域是 singleton 还是 prototype,默认是 singleton
	 */
	default boolean isSingleton() {
		return true;
	}

}

当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 factory.getBean("testbean") 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法返回的对象,相当于 FactoryBean#getObject() 代理了 getBean() 方法。例如:如果使用传统方式配置下面 User 的 <bean> 时, User 的每个属性分别对应一个 <property> 元素标签。

public class User {
	private String userName;
	private String email;
	...
}
	<bean id="user" class="org.pc.User">
		<property name="userName" value="zs"/>
		<property name="email" value="[email protected]"/>
	</bean>

如果使用 FactoryBean 的方式实现就会灵活一点,下面通过逗号分割符的方式一次性地为 User 的所有属性指定配置值:

import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {
	private String userInfo;

	public void setUserInfo(String userInfo) {
		this.userInfo = userInfo;
	}

	public String getUserInfo() {
		return userInfo;
	}

	@Override
	public User getObject() throws Exception {
		User user = new User();
		String[] infos = this.userInfo.split(",");
		user.setUserName(infos[0]);
		user.setEmail(infos[1]);
		return user;
	}

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

	@Override
	public boolean isSingleton() {
		return false;
	}
}
<bean id="user" class="org.pc.User" userInfo="zs,[email protected]"/>

当调用 factory.getBean("user") 时,Spring 通过反射机制发现 UserFactoryBean 实现了 FactoryBean 的接口,这时 Spring 容器就调用接口方法 UserFactoryBean#getObject() 方法返回。如果希望通过 factory.getBean("user") 获取 UserFactoryBean 实例,则需要在 beanName 加上前缀 “&”,例如:factory.getBean("&user")

二、尝试从缓存中获取单例 bean

介绍过 FactoryBean 的用法后,我们就可以了解 bean 的加载过程了。前面已经提到过,单例在 Spring 的同一个容器内只会被创建一次,后续再获取 bean 直接从单例缓存中虎丘,当然这里也只是尝试加载,首先尝试从缓存中加载,然后再次尝试从 sin个singletonFactories 中加载。因为在创建 bean 的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring 创建 bean 的原则是不等 bean 创建完成就会将创建 bean 的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 bean 创建时需要依赖上个 bean,则直接使用 ObjectFactory

  • AbstractBeanFactory#doGetBean(final String name, @Nullable final Class requiredType,@Nullable final Object[] args, boolean typeCheckOnly)
    • DefaultSingletonBeanRegistry#getSingleton(String beanName)
      • DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)
	/**
	 * DefaultSingletonBeanRegistry 类下的方法
	 */
	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		// 参数 true 设置标识容许早期依赖
		return getSingleton(beanName, true);
	}

	/**
	 * DefaultSingletonBeanRegistry 类下的方法
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//检查缓存中是否存在实例
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 如果为空 且 单例正在加载过程中,则锁定全局变量
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				// allowEarlyReference 为 true,则表明需要循环依赖检测
				if (singletonObject == null && allowEarlyReference) {
					/*
					 *     当某些方法需要提前初始化的时候则会调用 addSingletonFactory() 方法将对应的 ObjectFactory
					 * 初始化策略存储在 singletonFactories
					 *
					 * 补充说明:
					 *     在创建 bean 的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring 创建 bean
					 * 的原则是不等 bean 创建完成就会将创建 bean 的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 bean
					 * 创建时需要依赖上个 bean,则直接使用 ObjectFactory
					 */
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						// 调用预先设定的 getObject() 方法
						singletonObject = singletonFactory.getObject();
						// 记录在缓存中,earlySingletonObjects 和 singletonFactories 互斥
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

这个方法因为涉及循环依赖的检测,以及设计很多变量的记录存取,所以很容易让人摸不着头脑。这个方法首先尝试从 singletonObjects 里面获取实例,如果获取不到再从 earlySingletonObjects 里面获取,如果还是获取不到,再尝试从 singletonFactories 里面获取 beanName 对应的 ObjectFactory ,然后调用这个 ObjectFactorygetObject() 方法来创建 bean,并放到 earlySingletonObjects 里面去,并且从 singletonFactories 里面 remove 掉这个 ObjectFactory,而对于后续的所有内存操作都只是为了循环依赖检测时使用,也就是在 allowEarlyReference 为 true 的情况下才会使用。
这里涉及到用于存储 bean 的不同 map,简单解释如下:

  • singletonObjects:用于保存 beanName 和创建 bean 实例之间的关系,bean name -> bean instance
  • singletonFactories:用于保存 beanName 和创建 bean 的工厂之间的关系,bean name -> bean ObjectFactory
  • earlySingletonObjects:用于保存 beanName 和创建 bean 实例之间的关系,singletonObjects 的不同之处在于,当一个单例 bean 被放到这里面后,那么当 bean 还在创建过程中,就可以通过 getBean() 方法获取到了,其目的是用来检测循环引用。
  • registeredSingletons:用来保存当前所有已经注册的 bean
发布了444 篇原创文章 · 获赞 113 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/101033268