小伙伴们在面试的时候,有一个经典的问题:
Spring 中 FactoryBean 和 BeanFactory 有什么区别?
原本是风马牛不相及的两个东西,只是因为单词前后颠倒了一下,就变成了一个高频面试题!
在本系列前面文章中松哥在和大家分析 DefaultListableBeanFactory 容器的时候涉及到了一点点 BeanFactory 的知识,不过在那篇文章中 BeanFactory 毕竟不是主角,以后的文章松哥还会和大家再去探讨 BeanFactory 的功能。
今天我们就先来看看 FactoryBean 是什么?
只要我们分别将 FactoryBean 和 BeanFactory 的功能搞清楚,上面的那道面试题自然不在话下。
阅读本系列前面文章,有助于更好的理解本文:
Spring 源码解读计划
Spring 源码第一篇开整!配置文件是怎么加载的?
Spring 源码第二弹!XML 文件解析流程
Spring 源码第三弹!EntityResolver 是个什么鬼?
Spring 源码第四弹!深入理解 BeanDefinition
手把手教你搭建 Spring 源码分析环境
Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory
Spring 源码解读第七弹!bean 标签的解析
Spring 源码第 8 篇,各种属性的解析
1.一个 Demo 我们先从一个简单的 Demo 开始。
新建一个 Maven 项目,引入 Spring 依赖,然后创建一个 HelloService,如下:
1 2 3 4 5 public class HelloService { public String hello () { return "hello javaboy" ; } }
然后再创建一个 HelloService 的工厂类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class HelloServiceFactoryBean implements FactoryBean <HelloService > { @Override public HelloService getObject () throws Exception { return new HelloService(); } @Override public Class<?> getObjectType() { return HelloService.class; } @Override public boolean isSingleton () { return true ; } }
我们新建的 HelloServiceFactoryBean 类实现了 FactoryBean 接口,并指定了 HelloService 泛型。
接下来我们在 beans.xml 文件中配置 HelloServiceFactoryBean 实例:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean class ="org.javaboy.springdemo03.HelloServiceFactoryBean" id ="helloService" /> </beans >
加载该配置文件:
1 2 3 4 5 6 @Test void contextLoads () { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml" ); Object helloService = ctx.getBean("helloService" ); System.out.println(helloService.getClass().toString()); }
加载 XML 配置文件,并获取 Bean 实例,然后将 Bean 实例的类型打印出来。
按照我们之前的理解,这里获取到的 Bean 应该是 HelloServiceFactoryBean 的实例,但是实际打印结果如下:
1 class org.javaboy.springdemo03.HelloService
竟然是 HelloService!
到底怎么回事?我们得从 FactoryBean 接口开始讲起。
2.FactoryBean 接口 FactoryBean 在 Spring 框架中具有重要地位,Spring 框架本身也为此提供了多种不同的实现类:
按理说我们配置一个 Bean,直接在 XML 文件中进行配置即可。但是有的时候一个类的属性非常多,在 XML 中配置起来反而非常不方便,这个时候 Java 代码配置的优势就体现出来了,使用 FactoryBean 就可以通过 Java 代码配置 Bean 的相关属性。
从 Spring3.0 开始,FactoryBean 开始支持泛型,就是大家所看到的 FactoryBean 形式,FactoryBean 接口中一共有三个方法:
1 2 3 4 5 6 7 8 9 10 public interface FactoryBean <T > { @Nullable T getObject () throws Exception ; @Nullable Class<?> getObjectType(); default boolean isSingleton () { return true ; } }
getObject:该方法返回 FactoryBean 所创建的实例,如果在 XML 配置文件中,我们提供的 class 是一个 FactoryBean 的话,那么当我们调用 getBean 方法去获取实例时,最终获取到的是 getObject 方法的返回值。
getObjectType:返回对象的类型。
isSingleton:getObject 方法所返回的对象是否单例。
所以当我们调用 getBean 方法去获取 Bean 实例的时候,实际上获取到的是 getObject 方法的返回值,那么是不是就没有办法获取 HelloServiceFactoryBean 呢?当然是有的!
1 2 3 4 5 6 @Test void contextLoads () { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml" ); Object helloService = ctx.getBean("&helloService" ); System.out.println(helloService.getClass().toString()); }
打印结果如下:
1 class org.javaboy.springdemo03.HelloServiceFactoryBean
小伙伴们可以看到,只需要在 Bean 的名字前面加上一个 & 符号,获取到的就是 HelloServiceFactoryBean 实例了。
3.源码分析 根据 Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory 一文中的介绍,在 DefaultListableBeanFactory 中还有一个 preInstantiateSingletons 方法可以提前注册 Bean,该方法是在 ConfigurableListableBeanFactory 接口中声明的,DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口并实现了接口中的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Override public void preInstantiateSingletons () throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this ); } 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 FactoryBean) { final 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); } } } for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() != null ) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { smartSingleton.afterSingletonsInstantiated(); return null ; }, getAccessControlContext()); } else { smartSingleton.afterSingletonsInstantiated(); } } } }
preInstantiateSingletons 方法的整体逻辑比较简单,就是遍历 beanNames,对符合条件的 Bean 进行实例化,而且大家注意,这里所谓的提前初始化其实就是在我们调用 getBean 方法之前,它自己先调用了一下 getBean。
这里有几个比较关键的点。
第一个就是 isFactoryBean 方法的调用,该方法就是根据 beanName 去获取 Bean 实例,进而判断是不是一个 FactoryBean,如果没有 Bean 实例(还没创建出来),则根据 BeanDefinition 去判断是不是一个 FactoryBean。
如果是 FactoryBean,则在 getBean 时自动加上了 FACTORY_BEAN_PREFIX 前缀,这个常量其实就是 &,这样获取到的实例实际上就是 FactoryBean 的实例。获取到 FactoryBean 实例之后,接下来判断是否需要在容器启动阶段,调用 getObject 方法初始化 Bean,如果 isEagerInit 为 true,则去初始化。
按照我们前面的定义,这里获取到的 isEagerInit 属性为 false,即不提前加载 Bean,而是在开发者手动调用 getBean 的方法的时候才去加载。如果希望这里能够提前加载,需要重新定义 HelloServiceFactoryBean,并使之实现 SmartFactoryBean 接口,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class HelloServiceFactoryBean2 implements SmartFactoryBean <HelloService > { @Override public HelloService getObject () throws Exception { return new HelloService(); } @Override public Class<?> getObjectType() { return HelloService.class; } @Override public boolean isSingleton () { return true ; } @Override public boolean isEagerInit () { return true ; } }
实现了 SmartFactoryBean 接口之后,重写 isEagerInit 方法并返回 true,其他方法不变,重新配置 beans.xml 文件,然后启动容器。此时在容器启动时,就会提前调用 getBean 方法完成 Bean 的加载。
接下来我们来看 getBean 方法。
getBean 方法首先调用 AbstractBeanFactory#doGetBean,在该方法中,又会调用到 AbstractBeanFactory#getObjectForBeanInstance 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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; }
这段源码很有意思:
BeanFactoryUtils.isFactoryDereference 方法用来判断 name 是不是以 & 开头的,如果是以 & 开头的,表示想要获取的是一个 FactoryBean,那么此时如果 beanInstance 刚好就是一个 FactoryBean,则直接返回。并将 mbd 中的 isFactoryBean 属性设置为 true。
如果 name 不是以 & 开头的,说明用户就是想获取 FactoryBean 所构造的 Bean,那么此时如果 beanInstance 不是 FactoryBean 实例,则直接返回。
如果当前的 beanInstance 是一个 FactoryBean,而用户想获取的只是一个普通 Bean,那么就会进入到接下来的代码中。
首先调用 getCachedObjectForFactoryBean 方法去从缓存中获取 Bean。如果是第一次获取 Bean,这个缓存中是没有的数据的,getObject 方法调用过一次之后,Bean 才有可能被保存到缓存中了。
为什么说有可能呢?Bean 如果是单例的,则会被保存在缓存中,Bean 如果不是单例的,则不会被保存在缓存中,而是每次加载都去创建新的。
如果没能从缓存中加载到 Bean,则最终会调用 getObjectFromFactoryBean 方法去加载 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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); try { object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed" , ex); } finally { afterSingletonCreation(beanName); } } if (containsSingleton(beanName)) { this .factoryBeanObjectCache.put(beanName, object); } } } return object; } } else { Object object = doGetObjectFromFactoryBean(factory, beanName); if (shouldPostProcess) { try { object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed" , ex); } } return object; } }
在 getObjectFromFactoryBean 方法中,首先通过 isSingleton 和 containsSingleton 两个方法判断 getObject 方法返回值是否是单例的,单例的走一条路,非单例的走另外一条路。
如果是单例的:
先去缓存中再拿一次,看能不能拿到。如果缓存中没有,调用 doGetObjectFromFactoryBean 方法去获取,这是真正的获取方法。获取到之后,进行 Bean 的后置处理,处理完成后,如果 Bean 是单例的,就缓存起来。
如果不是单例的:
不是单例就简单,直接调用 doGetObjectFromFactoryBean 方法获取 Bean 实例,然后进行后置处理就完事,也不用缓存。
接下来我们就来看看 doGetObjectFromFactoryBean 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 { 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); } 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; }
做了一些判断之后,最终通过 factory.getObject(); 方法获取我们想要的实例。
这就是整个 FactoryBean 的加载流程。
4.应用 FactoryBean 在 Spring 框架中有非常广泛的应用,即使我们不写上面那段代码,也还是使用了不少的 FactoryBean。
接下来松哥随便举两个例子。
4.1 SqlSessionFactoryBean 这可能是大家接触最多的 FactoryBean,当 MyBatis 整合 Spring 时,我们少不了如下一行配置:
1 2 3 4 5 6 7 8 9 <bean class ="org.mybatis.spring.SqlSessionFactoryBean" id ="sqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="typeAliasesPackage" value ="org.javaboy.meeting.model" /> <property name ="mapperLocations" > <value > classpath*:org/javaboy/meeting/mapper/*.xml </value > </property > </bean >
这就是在配置 FactoryBean。当我们单独使用 MyBatis 时候,需要有一个 SqlSessionFactory(公众号江南一点雨后台回复 MyBatis 有 MyBatis 教程),现在整合之后,没有 SqlSessionFactory 了,我们有理由相信是 SqlSessionFactoryBean 的 getObject 方法提供了 SqlSessionFactory,我们来看下其源码:
1 2 3 4 5 6 7 @Override public SqlSessionFactory getObject () throws Exception { if (this .sqlSessionFactory == null ) { afterPropertiesSet(); } return this .sqlSessionFactory; }
确实如此!
4.2 Jackson2ObjectMapperFactoryBean 这也是一个大家使用相对较多的 FactoryBean。
如果项目中使用了 Jackson,同时希望在全局层面做一些配置,一般来说我们可能会用到这个类:
1 2 3 4 5 6 7 8 9 10 11 <mvc:annotation-driven conversion-service ="conversionService" > <mvc:message-converters > <bean class ="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" > <property name ="objectMapper" > <bean class ="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" > <property name ="simpleDateFormat" value ="yyyy-MM-dd" /> </bean > </property > </bean > </mvc:message-converters > </mvc:annotation-driven >
MappingJackson2HttpMessageConverter 类的 objectMapper 属性实际上是需要一个 ObjectMapper 对象,但是我们这里却提供了一个 Jackson2ObjectMapperFactoryBean,这是因为 Jackson2ObjectMapperFactoryBean 的 getObject 方法就是我们需要的 ObjectMapper:
1 2 3 4 5 @Override @Nullable public ObjectMapper getObject () { return this .objectMapper; }
FormattingConversionServiceFactoryBean 也曾经出现在松哥的 SpringMVC 教程中(公众号江南一点雨后台回复 springmvc 获取教程)。
如果前端传递的参数格式是 key-value 形式,那么日期类型的参数需要服务端提供一个日期类型转换器,像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 @Component public class DateConverter implements Converter <String , Date > { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd" ); public Date convert (String source) { try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null ; } }
然后在 XML 文件中对此进行配置:
1 2 3 4 5 6 7 8 <mvc:annotation-driven conversion-service ="conversionService" /> <bean class ="org.springframework.format.support.FormattingConversionServiceFactoryBean" id ="conversionService" > <property name ="converters" > <set > <ref bean ="dateConverter" /> </set > </property > </bean >
FormattingConversionServiceFactoryBean 中的 getObject 方法最终返回的是 FormattingConversionService。
类似的例子还有很多,例如 EhCacheManagerFactoryBean、YamlPropertiesFactoryBean 等,松哥就不一一赘述了,感兴趣的小伙伴可以自行查看。
5.小结 好啦,今天就和小伙伴们聊一聊 FactoryBean,讲了它的源码,也讲了它的用法,后面再来和大家聊一聊 BeanFactory,两个都聊完了了,文章一开始提出的面试题就不在话下了。
小伙伴们觉得有收获,记得点个在看鼓励下松哥哦~