- 2023-07-03 17:28:49
- 6200 热度
- 0 评论
小伙伴们在面试的时候,有一个经典的问题:
- 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 |
public class HelloService { |
然后再创建一个 HelloService 的工厂类:
1 |
public class HelloServiceFactoryBean implements FactoryBean<HelloService> { |
我们新建的 HelloServiceFactoryBean 类实现了 FactoryBean 接口,并指定了 HelloService 泛型。
接下来我们在 beans.xml 文件中配置 HelloServiceFactoryBean 实例:
1 |
<?xml version="1.0" encoding="UTF-8"?> |
加载该配置文件:
1 |
@Test |
加载 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
1 |
public interface FactoryBean<T> { |
- getObject:该方法返回 FactoryBean 所创建的实例,如果在 XML 配置文件中,我们提供的 class 是一个 FactoryBean 的话,那么当我们调用 getBean 方法去获取实例时,最终获取到的是 getObject 方法的返回值。
- getObjectType:返回对象的类型。
- isSingleton:getObject 方法所返回的对象是否单例。
所以当我们调用 getBean 方法去获取 Bean 实例的时候,实际上获取到的是 getObject 方法的返回值,那么是不是就没有办法获取 HelloServiceFactoryBean 呢?当然是有的!
1 |
@Test |
打印结果如下:
1 |
class org.javaboy.springdemo03.HelloServiceFactoryBean |
小伙伴们可以看到,只需要在 Bean 的名字前面加上一个 & 符号,获取到的就是 HelloServiceFactoryBean 实例了。
3.源码分析
根据 Spring 源码第六弹!無名和大家聊聊容器的始祖 DefaultListableBeanFactory 一文中的介绍,在 DefaultListableBeanFactory 中还有一个 preInstantiateSingletons 方法可以提前注册 Bean,该方法是在 ConfigurableListableBeanFactory 接口中声明的,DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口并实现了接口中的方法:
1 |
@Override |
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 |
public class HelloServiceFactoryBean2 implements SmartFactoryBean<HelloService> { |
实现了 SmartFactoryBean 接口之后,重写 isEagerInit 方法并返回 true,其他方法不变,重新配置 beans.xml 文件,然后启动容器。此时在容器启动时,就会提前调用 getBean 方法完成 Bean 的加载。
接下来我们来看 getBean 方法。
getBean 方法首先调用 AbstractBeanFactory#doGetBean,在该方法中,又会调用到 AbstractBeanFactory#getObjectForBeanInstance 方法:
1 |
protected Object getObjectForBeanInstance( |
这段源码很有意思:
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 |
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { |
在 getObjectFromFactoryBean 方法中,首先通过 isSingleton 和 containsSingleton 两个方法判断 getObject 方法返回值是否是单例的,单例的走一条路,非单例的走另外一条路。
如果是单例的:
先去缓存中再拿一次,看能不能拿到。如果缓存中没有,调用 doGetObjectFromFactoryBean 方法去获取,这是真正的获取方法。获取到之后,进行 Bean 的后置处理,处理完成后,如果 Bean 是单例的,就缓存起来。
如果不是单例的:
不是单例就简单,直接调用 doGetObjectFromFactoryBean 方法获取 Bean 实例,然后进行后置处理就完事,也不用缓存。
接下来我们就来看看 doGetObjectFromFactoryBean 方法:
1 |
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException { |
做了一些判断之后,最终通过 factory.getObject(); 方法获取我们想要的实例。
这就是整个 FactoryBean 的加载流程。
4.应用
FactoryBean 在 Spring 框架中有非常广泛的应用,即使我们不写上面那段代码,也还是使用了不少的 FactoryBean。
接下来無名随便举两个例子。
4.1 SqlSessionFactoryBean
这可能是大家接触最多的 FactoryBean,当 MyBatis 整合 Spring 时,我们少不了如下一行配置:
1 |
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean"> |
这就是在配置 FactoryBean。当我们单独使用 MyBatis 时候,需要有一个 SqlSessionFactory(公众号江南一点雨后台回复 MyBatis 有 MyBatis 教程),现在整合之后,没有 SqlSessionFactory 了,我们有理由相信是 SqlSessionFactoryBean 的 getObject 方法提供了 SqlSessionFactory,我们来看下其源码:
1 |
@Override |
确实如此!
4.2 Jackson2ObjectMapperFactoryBean
这也是一个大家使用相对较多的 FactoryBean。
如果项目中使用了 Jackson,同时希望在全局层面做一些配置,一般来说我们可能会用到这个类:
1 |
<mvc:annotation-driven conversion-service="conversionService"> |
MappingJackson2HttpMessageConverter 类的 objectMapper 属性实际上是需要一个 ObjectMapper 对象,但是我们这里却提供了一个 Jackson2ObjectMapperFactoryBean,这是因为 Jackson2ObjectMapperFactoryBean 的 getObject 方法就是我们需要的 ObjectMapper:
1 |
@Override |
4.3 FormattingConversionServiceFactoryBean
FormattingConversionServiceFactoryBean 也曾经出现在無名的 SpringMVC 教程中(公众号江南一点雨后台回复 springmvc 获取教程)。
如果前端传递的参数格式是 key-value 形式,那么日期类型的参数需要服务端提供一个日期类型转换器,像下面这样:
1 |
@Component |
然后在 XML 文件中对此进行配置:
1 |
<mvc:annotation-driven conversion-service="conversionService"/> |
FormattingConversionServiceFactoryBean 中的 getObject 方法最终返回的是 FormattingConversionService。
类似的例子还有很多,例如 EhCacheManagerFactoryBean、YamlPropertiesFactoryBean 等,無名就不一一赘述了,感兴趣的小伙伴可以自行查看。
5.小结
好啦,今天就和小伙伴们聊一聊 FactoryBean,讲了它的源码,也讲了它的用法,后面再来和大家聊一聊 BeanFactory,两个都聊完了了,文章一开始提出的面试题就不在话下了。
小伙伴们觉得有收获,记得点个在看鼓励下無名哦~
- Spring(403)
- Boot(208)
- Spring Boot(187)
- Spring Cloud(82)
- Java(82)
- Cloud(82)
- Security(60)
- Spring Security(54)
- Boot2(51)
- Spring Boot2(51)
- Redis(31)
- SQL(29)
- Mysql(25)
- Dalston(24)
- IDE(24)
- mongoDB(22)
- MVC(22)
- JDBC(22)
- IDEA(22)
- Web(21)
- CLI(20)
- Alibaba(19)
- SpringMVC(19)
- Docker(17)
- SpringBoot(17)
- Git(16)
- Eclipse(16)
- Vue(16)
- JPA(15)
- Apache(15)
- ORA(15)
- Tomcat(14)
- Linux(14)
- HTTP(14)
- Mybatis(14)
- Oracle(14)
- jdk(14)
- OAuth(13)
- Nacos(13)
- Pro(13)
- XML(13)
- JdbcTemplate(13)
- JSON(12)
- OAuth2(12)
- Data(12)
- int(11)
- Myeclipse(11)
- stream(11)
- not(10)
- Bug(10)
- Hystrix(9)
- ast(9)
- maven(9)
- Map(9)
- Swagger(8)
- APP(8)
- Bit(8)
- API(8)
- session(8)
- Window(8)
- windows(7)
- too(7)
- HTML(7)
- Github(7)
- JavaMail(7)
- Cache(7)
- File(7)
- IntelliJ(7)
- mail(7)
- Server(6)
- nginx(6)
- jar(6)
- ueditor(6)
- ehcache(6)
- UDP(6)
- RabbitMQ(6)
- and(6)
- star(6)
- Excel(6)
- Log4J(6)
- pushlet(6)
- apt(6)
- Freemarker(6)
- read(6)
- WebFlux(6)
- JSP(6)
- Bean(6)
- error(6)
- are(5)
- SVN(5)
- for(5)
- DOM(5)
- Sentinel(5)
- the(5)
- JWT(5)
- rdquo(5)
- PHP(5)
- Struts(5)
- string(5)
- script(5)