Spring注解驱动开发(七):利用FactoryBean向容器中注入组件


这是我Spring Frame 专栏的第七篇文章,在 Spring注解驱动开发(六):利用@Import向容器中注入组件 这篇文章中,我向你详细介绍了@Import注解的使用方式以及它在Spring中的应用场景,如果你未读过那篇文章,但是对内容感兴趣的话,我建议你去阅读一下

1. 背景介绍

在前面的文章中,我向你介绍了三种常见的向 Ioc 容器中注入组件的方式:

  • 利用 @ComponentScan+@Component 向容器中注入组件 (专栏第一篇文章讲述)
  • 利用 @Configuration+@Bean 向容器中注入组件 (专栏第二篇文章讲述)
    利用 @Import 向容器中注入组件
  • 利用 @Import 向容器中注入组件 (专栏第三篇文章讲述)

在本篇文章中,我会向你讲述如何利用FactoryBean接口向Spring容器中注册Bean

2. FacotyBean 使用

一般情况下,Spring Framework是通过反射来实例化bean的;但是某些Bean的属性极其复杂,需要编写很多的配置,于是Spring提供了一个FactoryBean的工厂类接口,用户可以定制Bean的生成方式

2.1 源码分析

按照惯例,我们先了解一下FactoryBean 接口信息,并从中总结知识:

/**
* Interface to be implemented by objects used within a {@link BeanFactory} which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b>
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
* <p>FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* <p>This interface is heavily used within the framework itself, for example for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure code.
*
* <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective facilities.</b>
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in the
* bootstrap process, even ahead of any post-processor setup. If you need access to
* other beans, implement {@link BeanFactoryAware} and obtain them programmatically.
*
* <p><b>The container is only responsible for managing the lifecycle of the FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean.</b> Therefore,
* a destroy method on an exposed bean object (such as {@link java.io.Closeable#close()}
* will <i>not</i> be called automatically. Instead, a FactoryBean should implement
* {@link DisposableBean} and delegate any such close call to the underlying object.
*
* <p>Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param <T> the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBean<T> {

// 获取Bean 对象
@Nullable
T getObject() throws Exception;


// 获取想要生成的Bean对象的类信息
@Nullable
Class<?> getObjectType();

// 决定 getObject()获取的Bean是否为单例的
default boolean isSingleton() {
	return true;
}
}

⭐️ 这里我并没有省略接口注释,是因为我建议你阅读源码时多看看注释,以便对类和方法有更加深刻的理解
从上面的源码中我们可以总结以下内容:

  • Spring Ioc 容器会对 FactoryBean 接口实现类进行完整的声明周期管理,并不会对我们想要定制的 Bean 进行管理,因此官方建议我们在实现类上实现 DisposableBean 接口,以委托给IoC容器关闭必要的资源
  • 实现了 FactoryBean 接口的类不再是一个普通的Bean,其公开的对象主要是我们向定制化的Bean实例
  • FactoryBean 接口在Spring Framework 底层有举足轻重的地位,在Spring 扩展定制中有重要的通
  • FactoryBean接口是一种编程风格,其并不依赖于注解驱动开发或者其它的反射实施方式
  • 实现定制化Bean的方法: getObject()方法
  • 决定定制化Bean作用域的方法: isSingleton()
2.2 简单使用

说了这么多理论,是为了让你对FactoryBean有一个整体认知,接下来我们就举一个例子,来展示以下它的用法:

  1. 先创建一个PersonFacotyBean,用来定制 Person 实例:
public class PersonFactoryBean implements FactoryBean {

   /**
    * 返回我们要定制化的Bean
    * @return
    * @throws Exception
    */
   @Override
   public Object getObject() throws Exception {
       return new Person(1,"jack","male");
   }

   /**
    * 返回定制化Bean的类型
    * @return
    */
   @Override
   public Class<?> getObjectType() {
       return Person.class;
   }
}

这里的代码很简单,我就不介绍了
2. 创建配置类,将 PersonFactoryBean 放入容器

public class IcoMain {
   public static void main(String[] args) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
       System.out.println("Ioc 容器初始化完成");
       System.out.println("----------BeanDefinitionNames-----------");
       String[] definitionNames = context.getBeanDefinitionNames();
       for (String definitionName : definitionNames) {
           System.out.println(definitionName);
       }
       System.out.println("----------Person对象-----------");
       Object o = context.getBean("personFactoryBean");
       System.out.println("personFactoryBean===>"+o);
       System.out.println("personFactoryBean.class===>"+o.getClass());
       context.close();
   }
}


接下来我们执行测试类,查看一下这个personFactoryBean对象
在这里插入图片描述
🐳 看到这里,你是否和当初的我一样对结果感到很好奇,但是至此你只要明白这个接口的用法就好了,一会我会向你解释这是为什么的

2.3 设置作用域

从上面的源码中我们看到 FactoryBean 有个 isSingleton() ,从名字就可以知道它决定了我们定制化Bean的作用域是singleton的还是prototype的.
这里我在 PersonFacotyBean 中覆盖这个方法并返回false,就可以实现每次调用都返回不同的Person实例:

public class PersonFactoryBean implements FactoryBean {

//......
   @Override
   public boolean isSingleton() {
       return false;
   }
}

修改测试类并执行:

public class IcoMain {
   public static void main(String[] args) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
       System.out.println("Ioc 容器初始化完成");
       System.out.println("----------BeanDefinitionNames-----------");
       String[] definitionNames = context.getBeanDefinitionNames();
       for (String definitionName : definitionNames) {
           System.out.println(definitionName);
       }
       System.out.println("----------Person对象-----------");
       Object o1 = context.getBean("personFactoryBean");
       Object o2 = context.getBean("personFactoryBean");
       System.out.println("o1 == o2===>"+ (o1 == o2));
       context.close();
   }
}

在这里插入图片描述
通过结果可以发现,我们每次都获取了一个新的 Person 对象
至此,我相信你已经对 FactoryBean 有了最基本的认知

2.4 获取 BeanFactory 实例

从上面你应该了解到了我们利用 BeanFactory 向 Ioc 容器注入Bean时获取到的都是我们定制的Bean实例,那么如何获取到 BeanFacory 对象本身呢?
⭐️ 其实很简单,只要在 personFactoryBean 前面加上 &就可以了

public class IcoMain {
   public static void main(String[] args) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
       System.out.println("Ioc 容器初始化完成");
       System.out.println("----------BeanDefinitionNames-----------");
       String[] definitionNames = context.getBeanDefinitionNames();
       for (String definitionName : definitionNames) {
           System.out.println(definitionName);
       }
       System.out.println("----------Person对象-----------");
       // 加上 & 
       Object o = context.getBean("&personFactoryBean");
       System.out.println("o===>"+o);
       context.close();
   }
}

在这里插入图片描述
从输出结果可以看到获取到了 PersonFactoryBean 的实例了

❓ 为什么会这样呢?其实你可以看一下 BeanFactoryFACTORY_BEAN_PREFIX属性及其注释就能明白了:

public interface BeanFactory {

/**
 * Used to dereference a {@link FactoryBean} instance and distinguish it from
 * beans <i>created</i> by the FactoryBean. For example, if the bean named
 * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
 * will return the factory, not the instance returned by the factory.
 */
String FACTORY_BEAN_PREFIX = "&";
}
2.5 BeanFacory,FactoryBean,ObjectFactory

BeanFacory,FactoryBean,ObjectFactory 这三个类你初次看到时是否和我一样分不清楚?
这里我给你解释以下他们三个的区别

  • BeanFactory: Spring IoC 的底层核心类,用来获取IoC 容器中的Bean对象
  • FactoryBean : 作为bean保存在单例对象容器里面(singletonObjects)
  • 如果直接通过beanName获取到的是FactoryBean通过getObject生成的对象
  • 如果想获取FactoryBean本身,需要通过&beanName来获取,容器根据前缀&和是否是FactoryBean,来从singletonObjects中获取bean
  • ObjectFactory: 一个普通的对象工厂,通过 getObject生成对象,不会在 IoC 容器中缓存
3. FactoryBean分析

在介绍 FactoryBean 的时候我留下了两个问题

  • FactoryBean 接口实现类对象是怎样存储到IoC容器中的
  • 从工厂中获取的 FactoryBean 接口实现类为什么是我们定制的Bean对象?
    这里,我便带你从源码的角度分析一下
3.1 IoC容器存储

这里我们先在PersonFactoryBean对象的空参构造器内打上断点,之后Debug模式运行,观察函数调用栈,找到对应的创建方法
在这里插入图片描述

:stat: 你可以顺着函数调用栈向下找到org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 方法就会看到里面查找对应的 PersonFactory 对象,之后调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean() 创建对应的实例

@Override
public void preInstantiateSingletons() throws BeansException {
	if (logger.isTraceEnabled()) {
		logger.trace("Pre-instantiating singletons in " + this);
	}

	// Iterate over a copy to allow for init methods which in turn register new bean definitions.
	// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

	// Trigger initialization of all non-lazy singleton beans...
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			if (isFactoryBean(beanName)) {
			// 先尝试在容器中获取对应的 PersonFactoryBean 对象
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				if (bean instanceof FactoryBean) {
					FactoryBean<?> factory = (FactoryBean<?>) bean;
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(
								(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
			}
			else {
				getBean(beanName);
			}
		}
	}
}

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	try {
		// 开始创建对应的 Bean
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}
	catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		// A previously detected exception with proper bean creation context already,
		// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
	}
}

从上面我们看到,Spring会向创建其它单例Bean一样先查看容器中是否有这个Bean,如果没有就创建这个 FactoryBean 的实现类,并将其和普通Bean一样存储到 IoC 容器

3.2 依赖查找获取Bean

我们主动调用 AnnotationConfigApplicationContext.getBean()和3.1中调用 getBean()有异曲同工之处(其实前者底层就是调用了后者)

那么我们就看看这个getBean() 是怎样找到我们定制的Bean的吧:

//org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String)
@Override
public Object getBean(String name) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(name);
}

其实 Spring 应用上下文也是通过 BeanFactory 来获取 Bean 的,这里由于篇幅原因我会向你展示一个调用链,你沿着调用链就能一步一步的看到本质了:
org.springframework.context.support.AbstractApplicationContext#getBean —>
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean —>
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getObjectForBeanInstance—>
org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean—>
org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
在最后这个方法内,你就可以看到其本质返回的对象会调用 getObject(),也就是会返回我们定制的Bean信息

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
		Object object;
		try {
			if (System.getSecurityManager() != null) {
				AccessControlContext acc = getAccessControlContext();
				try {
					object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				// 调用了 getObject()
				object = factory.getObject();
			}
		}
		catch (FactoryBeanNotInitializedException ex) {
			throw new BeanCurrentlyInCreationException(beanName, ex.toString());
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
		}

		// Do not accept a null value for a FactoryBean that's not fully
		// initialized yet: Many FactoryBeans just return null then.
		if (object == null) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(
						beanName, "FactoryBean which is currently in creation returned null from getObject");
			}
			object = new NullBean();
		}
		return object;
	}

好啦,到这里我相信你对FactoryBean的使用和原理有更深刻的了解了

4. 总结

这篇文章,我主要向你介绍了:

  • FactoryBean 接口的基本用啊
  • FactoryBean的源码分析
  • FactoryBean,BeanFactory,ObjectFactory 的差别
  • 获取 FactoryBean 的源码分析

最后,我希望你看完本篇文章后,能够在适当的时候使用FactoryBean接口向容器中注入组件,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值