松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->Spring Boot+Vue+微人事视频教程
小伙伴们在面试的时候,有一个经典的问题:
-
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,如下:
public class HelloService {
public String hello() {
return "hello javaboy";
}
}
然后再创建一个 HelloService 的工厂类:
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 实例:
<?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>
加载该配置文件:
@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 的实例,但是实际打印结果如下:
class org.javaboy.springdemo03.HelloService
竟然是 HelloService!
到底怎么回事?我们得从 FactoryBean 接口开始讲起。
2.FactoryBean 接口
FactoryBean 在 Spring 框架中具有重要地位,Spring 框架本身也为此提供了多种不同的实现类:
按理说我们配置一个 Bean,直接在 XML 文件中进行配置即可。但是有的时候一个类的属性非常多,在 XML 中配置起来反而非常不方便,这个时候 Java 代码配置的优势就体现出来了,使用 FactoryBean 就可以通过 Java 代码配置 Bean 的相关属性。
从 Spring3.0 开始,FactoryBean 开始支持泛型,就是大家所看到的 FactoryBean
形式,FactoryBean
接口中一共有三个方法:
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 呢?当然是有的!
@Test
void contextLoads() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Object helloService = ctx.getBean("&helloService");
System.out.println(helloService.getClass().toString());
}
打印结果如下:
class org.javaboy.springdemo03.HelloServiceFactoryBean
小伙伴们可以看到,只需要在 Bean 的名字前面加上一个 & 符号,获取到的就是 HelloServiceFactoryBean 实例了。
3.源码分析
根据 Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory 一文中的介绍,在 DefaultListableBeanFactory 中还有一个 preInstantiateSingletons 方法可以提前注册 Bean,该方法是在 ConfigurableListableBeanFactory 接口中声明的,DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口并实现了接口中的方法:
@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)) {
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);
}
}
}
// Trigger post-initialization callback for all applicable beans...
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 接口,如下:
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 方法:
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
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;
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
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。
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);
// Only post-process and store if not put there already during getObject() call above
// (e.g. because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet..
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 方法:
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);
}
// 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;
}
做了一些判断之后,最终通过 factory.getObject(); 方法获取我们想要的实例。
这就是整个 FactoryBean 的加载流程。
4.应用
FactoryBean 在 Spring 框架中有非常广泛的应用,即使我们不写上面那段代码,也还是使用了不少的 FactoryBean。
接下来松哥随便举两个例子。
4.1 SqlSessionFactoryBean
这可能是大家接触最多的 FactoryBean,当 MyBatis 整合 Spring 时,我们少不了如下一行配置:
<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,我们来看下其源码:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
确实如此!
4.2 Jackson2ObjectMapperFactoryBean
这也是一个大家使用相对较多的 FactoryBean。
如果项目中使用了 Jackson,同时希望在全局层面做一些配置,一般来说我们可能会用到这个类:
<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:
@Override
@Nullable
public ObjectMapper getObject() {
return this.objectMapper;
}
4.3 FormattingConversionServiceFactoryBean
FormattingConversionServiceFactoryBean 也曾经出现在松哥的 SpringMVC 教程中(公众号江南一点雨后台回复 springmvc 获取教程)。
如果前端传递的参数格式是 key-value 形式,那么日期类型的参数需要服务端提供一个日期类型转换器,像下面这样:
@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 文件中对此进行配置:
<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,两个都聊完了了,文章一开始提出的面试题就不在话下了。
小伙伴们觉得有收获,记得点个在看鼓励下松哥哦~
精彩文章推荐:
喜欢就点个"在看"呗^_^
本文分享自微信公众号 - 江南一点雨(a_javaboy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。