Spring中FactoryBean的作用和实现原理

摘要:
BeanFactory与FactoryBean,相信很多刚翻看Spring源码的同学跟我一样很好奇这俩货怎么长得这么像,分别都是干啥用的。BeanFactory是Spring中Bean工厂的顶层接口,也是我们常说的SpringIOC容器,它定下了IOC容器的一些规范和常用方法并管理着Spring中所有的Bean,今天我们不讲它,我们看一下后面那个FactoryBean。 先说下FactoryBean和其作用再开始分析:首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。

BeanFactory与FactoryBean,相信很多刚翻看Spring源码的同学跟我一样很好奇这俩货怎么长得这么像,分别都是干啥用的。BeanFactory是Spring中Bean工厂的顶层接口,也是我们常说的SpringIOC容器,它定下了IOC容器的一些规范和常用方法并管理着Spring中所有的Bean,今天我们不讲它,我们看一下后面那个FactoryBean。

先说下FactoryBean和其作用再开始分析:首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。


首发地址:https://www.guitu18.com/post/2019/04/28/33.html

上面的解释有点抽象,那么我们浏览一遍FactoryBean在Spring中是怎么应用的就好懂了。我们都知道在Spring中我们的Bean都会被Spring的IOC容器所管理,在AbstractApplicationContext中有一个很重要的方法:refresh(),项目启动或重启的时候refresh()会调用getBean()初始化所有的Bean,这个getBean()最终会指向AbstractBeanFactory中的getBean()方法。

在AbstractBeanFactory中,不管是根据名称还是根据类型,getBean()最终都会调用doGetBean(),在doGetBean()方法中一开始就获取了名称beanName和实例sharedInstance,这个方法太长,这里就贴前面两行。

 		String beanName = this.transformedBeanName(name);
        Object sharedInstance = this.getSingleton(beanName);

transformedBeanName(name)是为了获取Bean真正的名称,它会去掉name前面的'&',而getSingleton(beanName)是从父类容器singletonObjects中取的这个Bean的实例。在Spring中还有很多这样的容器,比如DefaultListableBeanFactory中的beanDefinitionMap,它就是的IOC容器真正保存Bean的地方,它是一个HashMap。类似的还有FactoryBeanRegistrySupport中的factoryBeanObjectCache等。

回到doGetBean()方法中,拿到sharedInstance后,后面的一大堆操作做了单例、多例等判断,最终会走到this.getObjectForBeanInstance(),关键部分就在这个方法中,进入方法代码。

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(this.transformedBeanName(name), beanInstance.getClass());
            }
        }
        if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
            Object object = null;
            if (mbd == null) {
                object = this.getCachedObjectForFactoryBean(beanName);
            }
            if (object == null) {
                FactoryBean<?> factory = (FactoryBean)beanInstance;
                if (mbd == null && this.containsBeanDefinition(beanName)) {
                    mbd = this.getMergedLocalBeanDefinition(beanName);
                }
                boolean synthetic = mbd != null && mbd.isSynthetic();
                object = this.getObjectFromFactoryBean(factory, beanName, !synthetic);
            }
            return object;
        } else {
            return beanInstance;
        }
    }

在上面的代码中有两个判断分别是beanInstance instanceof FactoryBeanBeanFactoryUtils.isFactoryDereference(name),前面判断的是beanInstance是否属于FactoryBean或其子类的实例,后面这个方法判断name是否不为空且以&开头。

    public static boolean isFactoryDereference(@Nullable String name) {
        return name != null && name.startsWith("&");
    }

如果beanInstance不属于FactoryBean或其子类的实例,或者name是以&开头就直接返回实例对象beanInstance,否则进入到if分支中。在if分支里的各种if .. ==null判断是为了提高性能,咱们只挑关键部分看:object = this.getObjectFromFactoryBean(factory, beanName, !synthetic); 继续跟踪进去看代码。

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
        if (factory.isSingleton() && this.containsSingleton(beanName)) {
            synchronized(this.getSingletonMutex()) {
                Object object = this.factoryBeanObjectCache.get(beanName);
                if (object == null) {
                    object = this.doGetObjectFromFactoryBean(factory, beanName);
                    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                    if (alreadyThere != null) {
                        object = alreadyThere;
                    } else {
                        if (shouldPostProcess) {
                            if (this.isSingletonCurrentlyInCreation(beanName)) {
                                return object;
                            }
                            this.beforeSingletonCreation(beanName);
                            try {
                                object = this.postProcessObjectFromFactoryBean(object, beanName);
                            } catch (Throwable var14) {
                                throw new BeanCreationException(beanName, 
                                            "Post-processing of FactoryBean's singleton object failed", var14);
                            } finally {
                                this.afterSingletonCreation(beanName);
                            }
                        }
                        if (this.containsSingleton(beanName)) {
                            this.factoryBeanObjectCache.put(beanName, object);
                        }
                    }
                }
                return object;
            }
        } else {
            Object object = this.doGetObjectFromFactoryBean(factory, beanName);
            if (shouldPostProcess) {
                try {
                    object = this.postProcessObjectFromFactoryBean(object, beanName);
                } catch (Throwable var17) {
                    throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", var17);
                }
            }
            return object;
        }
    }

这里面也是做了很多判断,咱们也只挑关键部分看。这里说一下个人想法,看源码的时候如果我们一直追究所有的细节那会让我们会越陷越深,掉入细节的无底洞,稍不留神脑回路跟不上就会蒙圈。我们要学会找源码中的关键部分看,弄懂主要流程和本次看源码的目的的那部分就行。等我们对Spring整体有了一个很好的理解之后,再回头看之前不懂的代码就会豁然开朗。在上面这个方法中不管是走上面的if分支还是到下边的else中,关键部分就是object = this.doGetObjectFromFactoryBean(factory, beanName)这段代码调用,继续点进去。

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
        Object object;
        try {
            if (System.getSecurityManager() != null) {
                AccessControlContext acc = this.getAccessControlContext();
                try {
                    object = AccessController.doPrivileged(factory::getObject, acc);
                } catch (PrivilegedActionException var6) {
                    throw var6.getException();
                }
            } else {
                object = factory.getObject();
            }
        } catch (FactoryBeanNotInitializedException var7) {
            throw new BeanCurrentlyInCreationException(beanName, var7.toString());
        } catch (Throwable var8) {
            throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", var8);
        }
        if (object == null) {
            if (this.isSingletonCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName, 
                        "FactoryBean which is currently in creation returned null from getObject");
            }
            object = new NullBean();
        }
        return object;
    }

在这个方法中有一行关键代码:object = factory.getObject(); 这个factory就是我们传入的beanInstance实例。绕了这么一大圈,getBean方法返回的居然是我们实现FactoryBean接口定义的getObject方法。

那么跟一开始对FactoryBean的描述印证了,FactoryBean是一个能生产或修饰对象生成的工厂Bean。一个Bean如果实现了FactoryBean接口,那么根据该Bean的名称获取到的实际上是getObject()返回的对象,而不是这个Bean自身实例,如果要获取这个Bean自身实例,那么需要在名称前面加上'&'符号

一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式


原理弄明白了,下面通过代码测试验证上面的流程,先定义一个Bean实现FactoryBean接口。

@Component
public class MyBean implements FactoryBean {
    private String message;
    public MyBean() {
        this.message = "通过构造方法初始化实例";
    }
    @Override
    public Object getObject() throws Exception {
        MyBean myBean = new MyBean();
        myBean.message = "通过FactoryBean.getObject()创建实例";
        // 这里并不一定要返回MyBean自身的实例,可以是其他任何对象的实例
        return myBean;
    }
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
    public String getMessage() {
        return message;
    }
}

MyBean实现了FactoryBean接口的两个方法,getObject()是可以返回任何对象的实例的,这里测试就返回MyBean自身实例,且返回前给message字段赋值。同时在构造方法中也为message赋值。然后测试代码中先通过名称获取Bean实例,打印message的内容,再通过'&'+名称获取实例并打印message内容。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class FactoryBeanTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void test() {
        MyBean myBean1 = (MyBean) context.getBean("myBean");
        System.out.println("myBean1 = " + myBean1.getMessage());
        MyBean myBean2 = (MyBean) context.getBean("&myBean");
        System.out.println("myBean2 = " + myBean2.getMessage());
        System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
    }
}
myBean1 = 通过FactoryBean.getObject()初始化实例
myBean2 = 通过构造方法初始化实例
myBean1.equals(myBean2) = false

通过测试我们发现获取的两个实例中的message的值不一样,对比两个对象的引用也不相同。上述所讲关于FactoryBean的内容印证完毕,本文结束。

免责声明:文章转载自《Spring中FactoryBean的作用和实现原理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇对已存在的表进行分区时遇到的坑看大数据时代下的IT架构(1)业界消息队列对比下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Spring(七)Spring中的四种增强和顾问

Spring中的四种增强有那四种? 前置增强 后置增强 环绕增强 异常增强 先编写接口和实体类 ISomeService和SomeServiceImpl package demo10; /** * Created by mycom on 2018/3/8. */ public interfaceISomeService { public vo...

Java各种反射性能对比

对各种方法实现get方法的性能进行了一个测试。 总共有5个测试,,每个测试都是执行1亿次 1. 直接通过Java的get方法 2.通过高性能的ReflectAsm库进行测试 3.通过Java Class类自带的反射获得Method测试 4.使用Java自带的Property类获取Method测试 5.BeanUtils的getProperty测试 1 测试...

springboot整合filter

新建一个项目       新建Firstfilter类   Firstfliter.java package com.example.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import j...

两个springboot项目如何部署在同一个服务上,两个spring启动应用程序部署在同一台服务器上

现在有两个spring boot的maven项目,需要部署到同一个外部的服务上(tomcat),他们都包含spring-boot-starter-actuator(健康检查)的依赖,但是出现下面的异常 我有多个spring引导应用程序(maven项目),其中包含“spring-boot-starter-actuator”依赖项,用于检查每个应用程序的运行状...

SSH三大框架整合步骤

Struts2:需要整合的第一个框架: 1.创建一个动态web项目 2.导入struts2必须的jar 放到 lib目录下 ,再 build path 添加web工程中 3.配置struts2的核心配置文件:struts.xml 4.在web.xml文件中添加struts2的核心过滤器 5.添加测试页面 6.导入tomcat jar包 对j2e...

Spring boot 使用profile完成不同环境的maven打包功能

<profiles> <profile> <id>dev</id> <properties> <!-- 环境标识,需要与配置文件的名称相对应 --> <activatedProperties>dev</activatedPropert...