文章目录
这是我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有一个整体认知,接下来我们就举一个例子,来展示以下它的用法:
- 先创建一个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 的实例了
❓ 为什么会这样呢?其实你可以看一下
BeanFactory
的FACTORY_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接口向容器中注入组件,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!