2335 字
约 7 分钟
17
S07-X02 · BeanFactory 与 FactoryBean:一字之差的两件事

把两个词倒过来念,很多人就分不清了。BeanFactory 是容器——你向它要 Bean;FactoryBean 是被容器管理的工厂——它生产另一个对象。一个是入口,一个是插件,名字相似纯属历史巧合。

真正绊倒人的不是定义,而是三个后果:getBean("x") 拿到的可能不是那个 Bean、&x 加在哪、类型注入为什么失效。代码按 Spring Framework 6.1 / Spring Boot 3.3.x 核对。


1. BeanFactory:容器的最小接口

BeanFactory 是 IoC 容器的最底层契约,核心就两件事:按名字/类型取 Bean、查询 Bean 的元信息。

能力 BeanFactory ApplicationContext(继承并扩展)
getBean(name/type) 有 有
单例缓存、作用域 有 有
层级容器(getParentBeanFactory) 有 有
自动注册 BeanPostProcessor 有 有
自动注册 BeanFactoryPostProcessor 不自动 自动
国际化 MessageSource 无 有
事件 ApplicationEventPublisher 无 有
资源加载 ResourcePatternResolver 无 有
@Autowired 注解处理(AutowiredAnnotationBeanPostProcessor) 需手动注册 自动
启动即实例化单例(refresh()) 无 有
BeanFactory bf = new DefaultListableBeanFactory();   // 裸容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); // 完整容器

结论:业务代码别注入 BeanFactory 做依赖装配,它拿不到注解驱动的能力,等于把 Spring 降级成手写工厂;只有写框架层(如 BeanFactoryPostProcessor)时它才是对的参数类型。

层级容器:getBean 找不到时只向上找父容器,父容器不向下找——这就是"子容器能看到父容器的 Bean,反之不行"的机制。


2. FactoryBean:容器里的一台"生产机"

FactoryBean<T> 只有三个方法:

方法 返回 作用 不实现会怎样
T getObject() 产品对象 真正被注入的东西 必须实现
Class<?> getObjectType() 产品类型 类型匹配的依据 默认返回 null,类型注入失效
boolean isSingleton() 是否单例 产品是否缓存 默认 true(产品被缓存)
public class RetryProxyFactoryBean implements FactoryBean<OrderService> {
    private final OrderService target;
    public RetryProxyFactoryBean(OrderService target) { this.target = target; }

    @Override public OrderService getObject() {
        return (OrderService) Proxy.newProxyInstance(getClass().getClassLoader(),
            new Class[]{OrderService.class},
            (p, m, a) -> { try { return m.invoke(target, a); }
                           catch (InvocationTargetException e) { throw e.getCause(); } });
    }
    @Override public Class<?> getObjectType() { return OrderService.class; }
    @Override public boolean isSingleton() { return true; }
}

注册后,容器里有两条"路径"指向同一个定义:

  • getBean("retryProxy") → 返回 getObject() 的产物(那个代理对象);
  • getBean("&retryProxy") → 返回 RetryProxyFactoryBean 本身。
System.out.println(ctx.getBean("retryProxy").getClass());   // jdk.proxy1.$Proxy52
System.out.println(ctx.getBean("&retryProxy").getClass());  // com.example.RetryProxyFactoryBean

& 前缀是 BeanFactory.FACTORY_BEAN_PREFIX,不是语法糖,而是容器区分"要产品"和"要工厂"的唯一开关。

调用 拿到 适用场景
getBean("x") getObject() 的返回值 业务注入
getBean("&x") FactoryBean 实例 取工厂做配置、调工厂自定义方法
getBean(x.class) 若产品类型匹配则返回产品 类型注入
getBean(&x.class) 不合法 前缀只用于按名查找

3. 谁在用 FactoryBean

组件 FactoryBean 实现 生产什么
MyBatis MapperFactoryBean Mapper 接口的动态代理
Spring ORM LocalContainerEntityManagerFactoryBean EntityManagerFactory
Spring ORM SqlSessionFactoryBean SqlSessionFactory
AOP ProxyFactoryBean 声明式代理对象
远程调用 RmiProxyFactoryBean、HessianProxyFactoryBean 远程服务本地代理

共同特征:最终对象需要复杂装配或被包装,而这个过程依赖容器环境。以 MyBatis 为例:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
// 从这个定义里取出来的直接就是可用的 Mapper 代理
UserMapper mapper = ctx.getBean(UserMapper.class);

这也解释了那个常见困惑:"Mapper 是接口、没写实现,为什么能 @Autowired?"——容器注册的是 MapperFactoryBean,注入的是它的产品。


4. FactoryBean 与 @Bean 方法:何时还需要前者

@Bean 方法已经能返回任意对象,绝大多数场景它更简洁。两者真正的差别在类型已知性与作用域语义:

维度 @Bean 方法 FactoryBean
声明位置 @Configuration 类里的方法 一个实现类 + 注册
返回对象类型 由方法返回类型决定 由 getObjectType() 运行期决定(可动态)
是否多一层间接 否,方法返回即产品 是,产品由 getObject() 产生
&name 取工厂 不适用(除非返回的实例本身是 FactoryBean) 适用
适合场景 静态装配、一次性构造 产品的类型/数量在运行期才确定、需要被复用为基础设施
生命周期耦合 低 高,可实现 InitializingBean
生态兼容 需要自己处理 MyBatis/ORM/AOP 等既有组件已按此约定实现

判断规则两条:产品类型在编译期确定、且不需被别的组件按"工厂"识别 → @Bean;产品类型在运行期才确定、或要对接按 FactoryBean 约定工作的既有组件 → FactoryBean。

需要提醒的是:@Bean 方法返回 FactoryBean 实例时,容器同样会走 FactoryBean 语义——因为容器是在拿到实例后用 instanceof FactoryBean 判断的,与定义方式无关。此时想要工厂本身,依然要写 &name。


5. 三个容易混的名字

名字 属于哪一层 时机 能做什么 典型用途
FactoryBean Bean 定义层 取 Bean 时 决定"这个 Bean 是什么" 代理、适配、动态类型
BeanFactoryPostProcessor Bean 定义层 所有 Bean 实例化之前 改 BeanDefinition(作用域、属性、开关) 占位符解析、条件注册、动态改配置
BeanPostProcessor Bean 实例层 每个 Bean 初始化前后 包装、代理、注入增强 @Autowired 处理、AOP 代理、@PostConstruct

一句话:BeanFactoryPostProcessor 动"蓝图",BeanPostProcessor 动"成品",FactoryBean 决定"成品从哪来"。


6. SmartFactoryBean 与类型推断

SmartFactoryBean<T> 是 FactoryBean 的子接口,多出两个方法:

方法 含义 不实现(默认)
isEagerInit() 容器启动时就调用 getObject() false,等第一次被用到才创建产品
isPrototype() 产品是否 prototype 语义 false
public class ExpensiveBeanFactoryBean implements SmartFactoryBean<ExpensiveBean> {
    @Override public ExpensiveBean getObject() { return build(); }
    @Override public Class<?> getObjectType() { return ExpensiveBean.class; }
    @Override public boolean isEagerInit() { return true; }   // 启动即预热
}

isEagerInit 用于启动期预热:把首次调用的代价挪到启动阶段,代价是启动变慢、构建失败即启动失败。

类型查询则更微妙:

String[] names = ctx.getBeanNamesForType(DataSource.class);
String[] fast  = ctx.getBeanNamesForType(DataSource.class, true, false); // allowEagerInit=false

getBeanNamesForType(type) 对 FactoryBean 的默认行为是问 getObjectType();返回 null 时,在允许 eager init 的情况下容器会实例化工厂并调 getObject() 探明类型,可能触发昂贵初始化甚至循环依赖——allowEagerInit=false 就是关掉这个探测。


7. 常见坑

坑 现象 原因 处理
漏写 & 拿到的是代理/产品,调用工厂自定义方法报 ClassCastException getBean("x") 走的是产品路径 显式 getBean("&x")
getObjectType() 返回 null 按类型注入失败、NoSuchBeanDefinitionException 容器无法判断产品类型 返回明确类型;泛型可用 ResolvableType
getObject() 里做重活 启动慢或首次请求卡顿 创建时机被 isEagerInit 或 eager 探测触发 懒建,或用 ObjectProvider
在 getObject() 里依赖自身产品 BeanCurrentlyInCreationException 工厂与产品互相依赖 拆成两个 Bean,或注入 ObjectProvider
把 FactoryBean 当 BeanFactoryPostProcessor 用 改不了其他 Bean 的定义 时机与职责都不同 改定义用 BeanFactoryPostProcessor

最后一条是"名字混淆"的典型事故:想在启动时替换某个 Bean 的实现,却写了 FactoryBean,结果只换掉自己产的这一个产品。


8. 边界:什么时候不该用 FactoryBean

场景 更合适的做法 理由
只是构造一个复杂对象 @Bean 方法 + 构造器注入 少一层间接,栈浅、易读
需要按环境返回不同实现 @Conditional / @Profile 声明式判断比运行期分支清晰
需要动态数量的 Bean ImportBeanDefinitionRegistrar 面向定义层而非产品层
只是想要一个静态工厂方法 @Bean 里直接调它 已经够了
需要 AOP 代理 @Aspect 或 @Transactional 已有成熟代理机制,不必手搓

一句话:FactoryBean 是给"产品类型在运行期才确定、且需要被基础设施识别"的场景准备的;其余情况,@Bean 方法几乎总是更短的路径。


9. 自测

  1. getBean("userMapper") 与 getBean("&userMapper") 分别返回什么?
  2. BeanFactory 与 ApplicationContext 最关键的能力差异是哪三条?
  3. FactoryBean.getObjectType() 返回 null 会带来哪些具体后果?
  4. @Bean 方法返回一个 FactoryBean 实例时,容器注入的到底是什么?
  5. FactoryBean、BeanFactoryPostProcessor、BeanPostProcessor 各自的作用时机是什么?
  6. SmartFactoryBean.isEagerInit() 返回 true 会影响启动速度还是首次请求,为什么?
S07-X02 · BeanFactory 与 FactoryBean:一字之差的两件事
https://www.clxhxhhr.top/posts/5797/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。