前言
开心一刻
那年去相亲, 地点在饭店里, 威特先上了两杯水, 男方绅士的喝了一口, 咧嘴咋舌轻放桌面, 手抚额头闭眼一脸陶醉, 白水硬是喝出了 82 年拉菲的感觉. 如此有生活情调的幽默男人, 果断拿下, 相处后却发现他比较木讷, 问他为什么那天喝水那么有趣, 他仰头道: 鬼知道那杯水怎么那么烫啊!
是什么
FactoryBean 的源码比较简单, 大家可以细读下其注释, 我做了简单的如下翻译
- /**
- * 实现此接口的 bean 不能用作普通 bean. 此 bean 暴露的对象是通过 getObject()创建的对象, 而不是它自身
- */
- public interface FactoryBean<T> {
- /**
- * 返回此工厂管理的对象的实例 (可能是共享的或独立的, 取决于 isSingleton() 的返回值)
- */
- @Nullable
- T getObject() throws Exception;
- /**
- * 返回此 FactoryBean 创建的对象类型,
- */
- @Nullable
- Class<?> getObjectType();
- /**
- * 该工厂管理的对象是否为单例?
- * 如果是 (return true),getObject() 总是返回同一个共享的实例, 该对象会被 BeanFactory 缓存起来
- * 如果是 (return false),getObject() 返回独立的实例
- * 一般情况下返回 true
- */
- default boolean isSingleton() {
- return true;
- }
- }
说的简单点, FactoryBean 是 BeanFactory 支持的, 用来暴露 bean 实例的接口
有什么用
先带大家回忆下, 目前我们配置 bean 主要有哪几种方式?
1, 基于 xml 的配置方式
在 xml 文件中配置, 例如
- <?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-4.1.xsd">
- <bean id="user" class="com.lee.factorybean.User">
- <property name="name" value="zhangsan"/>
- </bean>
- </beans>
spring 的发布的第一版就支持, 这个大家都知道
2, 基于注解的配置方式
spring2.5 开始支持, 例如:@Compoment,@Repository,@Controller,@Service 等, 平时我们用的挺多的
3, 基于 Java 类的配置方式
spring3.0 开始支持, 也是目前 spring 推荐的方式,@Configuration 结合 @Bean,springboot 中用的非常多
一般情况下, Spring 通过反射机制利用 < bean > 的 class 属性指定实现类实例化 Bean, 在某些情况下, 实例化 Bean 过程比较复杂, 如果按照传统的 xml 方式, 则需要在 < bean > 中提供大量的配置信息. xml 配置方式的灵活性是受限的, 这时采用编码的方式可能会得到一个简单的方案. 那么编码方式又有哪些了? spring3.0 之后, 编码的方式有基于注解, 基于 Java 类以及基于 FactoryBean, 那么在 spring2.5 之前了, 如何用 xml 方式配置实例化过程比较复杂的 Bean? 可以采用 xml 结合 FactoryBean 来实现, xml 中配置 FactoryBean,FactoryBean 创建我们需要的, 实例化过程比较复杂的 Bean, 示例核心代码如下, 从 spring 容器获取 name 为 user 的 bean 实例, 获取到的是 User 类型的 Bean
xml 配置
- <?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-4.1.xsd">
- <bean id="user" class="com.lee.factorybean.config.UserFactoryBean" />
- </beans>
- UserFactoryBean
- package com.lee.factorybean.config;
- import com.lee.factorybean.entity.User;
- import org.springframework.beans.factory.FactoryBean;
- public class UserFactoryBean implements FactoryBean<User> {
- @Override
- public User getObject() throws Exception {
- // 假设 User 的实例化过程比较复杂, 在此处进行 User 的实例化
- return new User();
- }
- @Override
- public Class<?> getObjectType() {
- return User.class;
- }
- @Override
- public boolean isSingleton() {
- return true;
- }
- }
spring2.5 之前, 只能通过 xml 的配置方式将 Bean 注册到 spring 管理, 但是 xml 的配置方式又不够灵活, 配置实例化过程比较复杂的 Bean 比较麻烦, 所有结合 FactoryBean, 既能采用编码的方式构建实例化过程比较复杂的 Bean, 也能将 Bean 交由 Spring 管理; spring2.5 之后, 特别是 spring3.0 之后, 注册实例化过程比较复杂的 Bean 到 spring 容器的方式就比较多了(可采用的编码方式比较多),FactoryBean 的方式也一直被 spring 支持.
说的再简单点, 通过 FactoryBean 可以创建实例化过程比较复杂的 Bean, 至于我们以何种方式将 FactoryBean 的实例注册到 Spring 容器, 在不同的 spring 版本, 可以采用不同的方式
怎么用
我们通过一个简单的示例来看看 FactoryBean 到底是怎么用的
应用示例
示例地址: spring-boot-FactoryBean
UserFactoryBean
- package com.lee.factorybean.config;
- import com.lee.factorybean.entity.User;
- import org.springframework.beans.factory.FactoryBean;
- import org.springframework.stereotype.Component;
- @Component("user") // beanName = user
- public class UserFactoryBean implements FactoryBean<User> {
- @Override
- public User getObject() throws Exception {
- // 假设 User 的实例化过程比较复杂, 在此处进行 User 的实例化
- return new User();
- }
- @Override
- public Class<?> getObjectType() {
- return User.class;
- }
- @Override
- public boolean isSingleton() {
- return true;
- }
- }
- View Code
- User
- package com.lee.factorybean.entity;
- public class User {
- private Integer id;
- private String name;
- private Integer age;
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- }
- View Code
- FactoryBeanTest
- package com.lee.factorybean.test;
- import com.lee.factorybean.FactoryBeanApplication;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.BeanFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class FactoryBeanTest {
- @Autowired
- private BeanFactory beanFactory;
- @Test
- public void test() {
- Object user = beanFactory.getBean("user");
- System.out.println(user.getClass().getName());
- }
- }
- View Code
我们运行测试用例, 发现输出的结果是: com.lee.factorybean.entity.User, 而不是: com.lee.factorybean.config.UserFactoryBean
spring 中的 FactoryBean 实现
Spring 自身就提供了很多 FactoryBean 的实现 (spring 版本不一样, 实现数量不一样),springboot2.0.3(对应 spring5.0.7) 中 FactoryBean 实现有如下
除了我们自定义的 UserFactoryBean, 有 60 个是 springboot 中的实现, 其中有 50 多个是 spring 中的实现; 有兴趣的可以细看下, 注意 springboot 版本, 如果直接用的 spring, 则注意 spring 的版本
实际工作中, 我们自己实现 FactoryBean 的场景非常少, 反正我工作中是用的非常少, 印象中有, 但感觉是很久之前的事了; Spring 中有很多 FactoryBean 的实现, 也有很多第三方的实现, 比如 MyBatis 的 MapperFactoryBean,druid 的 JdbcStatManagerFactoryBean,shiro 的 ShiroFilterFactoryBean 等等. 用不用 FactoryBean, 全看我们个人, 但我们一定得知道 FactoryBean, 当我们碰到 FactoryBean 的实现时(读源码很容易碰到), 我们一眼就能明白其意图, 当我们需要构建实例化过程比较复杂的 Bean 时, FactoryBean 也是一种可选的方案
为什么
具体问题应该是这样的: 上述示例中, 为什么从 spring 容器获取的 name 为 user 的实例, 其类型是 User, 而不是 UserFactoryBean; 抽象的问题: 根据 FactoryBean 实例的 name 获取的为什么不是 FactoryBean 实例, 而是 FactoryBean 实例的 getObject()返回的对象?
源码探究
我们就以 beanFactory.getBean("user"); 为断点入口
- @Test
- public void test() {
- // 断点入口
- Object user = beanFactory.getBean("user");
- System.out.println(user.getClass().getName());
- }
一开始从 spring 容器获取名为 user 的 bean, 类型确实是: UserFactoryBean, 但是后面又经过 getObjectForBeanInstance 来真正获取我们需要的对象
- bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
- /**
- * 获取实例对象
- * 可能是 beanInstance 自身, 也可能是 beanInstance 创建的对象(如果 beanInstance 是 FactoryBean 类型)
- */
- protected Object getObjectForBeanInstance(
- Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
- // name 以工厂引用 (&) 开头, 可以跟下 isFactoryDereference 方法
- if (BeanFactoryUtils.isFactoryDereference(name)) {
- if (beanInstance instanceof NullBean) {
- return beanInstance;
- }
- // 如果 name 以 & 开头, 而 beanInstance 不是 FactoryBean 类型, 则抛异常(我们没按 spring 规则来使用)
- if (!(beanInstance instanceof FactoryBean)) {
- throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
- }
- }
- // 如果 beanInstance 不是 FactoryBean 类型, 则直接返回 beanInstance
- // 或者 name 以 & 开头, 也直接返回 beanInstance, 说明我们就想获取 FactoryBean 实例
- if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
- return beanInstance;
- }
- Object object = null;
- if (mbd == null) {
- object = getCachedObjectForFactoryBean(beanName);
- }
- if (object == null) {
- // 此时 beanInstance 是 FactoryBean 类型, 而 name 又不是以 & 开头; 这是我们示例工程的情况, 也是最普通, 用的最多的情况
- // 将 beanInstance 强转成 FactoryBean 类型
- FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
- // 从缓存中获取我们需要的实例对象
- if (mbd == null && containsBeanDefinition(beanName)) {
- mbd = getMergedLocalBeanDefinition(beanName);
- }
- boolean synthetic = (mbd != null && mbd.isSynthetic());
- // 调用 FactoryBean 的 getObject 方法创建我们需要的实例对象; 大家自行跟下 getObjectFromFactoryBean
- object = getObjectFromFactoryBean(factory, beanName, !synthetic);
- }
- return object;
- }
- View Code
根据 name 从 spring 容器获取实例, 如果该实例不是 FactoryBean 类型, 则直接返回该实例, 这也是我们平时用的最多的, 最普通的情况; 如果该实例是 FactoryBean 类型, 而 name 又是以 & 开头, 也直接返回该实例, 说明我们想要的就是 FactoryBean 实例; 如果 name 不是以 & 开头, 而该实例又是 FactoryBean 类型, 则会调用该实例的 getObject()来创建我们需要的目标实例
如何获取 FactoryBean 实例
这个答案在上面已经有了, 通过在 name 前加 & 即可, 如下
- @Test
- public void test() {
- Object user = beanFactory.getBean("&user");
- System.out.println(user.getClass().getName());
- }
输出结果如下
com.lee.factorybean.config.UserFactoryBean
总结
1,FactoryBean 是 BeanFactory 支持的, 用来暴露 bean 实例的接口, 可以实现此接口来完成实例化过程比较复杂的 bean 的创建;
2, 通过 beanName 从 spring 容器获取 bean 实例时, 一开始获取的是 beanName 直接关联的 bean 实例, 后续 spring 容器会根据此 bean 实例返回我们需要的对象实例; 如果 bean 实例不是 FactoryBean 类型, 则直接返回 bean 实例, 如果 bean 实例是 FactoryBean 类型, 而 beanName 又是以 & 开头, 直接返回 bean 实例, 如果 bean 实例是 FactoryBean 类型, 而 beanName 不是以 & 开头, 则返回 bean 实例的 getObject()方法获取的对象实例(一般 getObject 中就是我们需要的实例对象的创建过程);
3, 对于创建过程比较复杂的对象的创建, 目前 spring 其实有很多实现方式了, 而 FactoryBean 只是其中一种, 也许我们不会采用此种方式来实现实例对象的创建, 但我们需要能够看懂此种方式, 知道有这种实现方式; 很多第三方都沿用了此种方式, 我们去追源码的时候, 很容易就能碰到;
4, 相比普通 bean 的创建, FactoryBean 的方式会在 spring 容器中多存在一个 FactoryBean 的实例, 若想获取 FactoryBean 实例对象, 只需要在 FactoryBean 的 beanName 加 & 即可;
来源: https://www.cnblogs.com/youzhibing/p/10528821.html