1. 简介

工厂方法(Factory Method)是一种非常实用的设计模式,它能把复杂的对象创建逻辑封装在一个方法里,对外提供简单调用接口✅。在 Spring 中,虽然我们日常更多使用构造器注入或字段注入来创建 Bean,但其实还有一种更灵活的方式 —— 通过工厂方法创建 Spring Bean

本文将深入讲解如何使用实例工厂方法和静态工厂方法来创建 Spring Bean,涵盖无参和带参场景,帮你掌握这一“冷门但关键”的技能,尤其在集成遗留系统或处理复杂初始化逻辑时非常有用。

💡 什么时候该用工厂方法?
当你的对象创建过程涉及多步骤初始化、条件判断、资源加载、或调用非 Spring 管理的第三方工厂时,用工厂方法能有效解耦配置与创建逻辑。


2. 实例工厂方法(Instance Factory Method)

实例工厂方法指的是:先有一个工厂类的实例,再通过它的某个普通方法(非静态)来创建目标 Bean。Spring 会先管理工厂类,再用它生成最终 Bean。

2.1 无参创建

先定义一个简单的 Foo 类,作为我们要创建的 Bean:

public class Foo {}

接着写一个工厂类 InstanceFooFactory,里面提供一个 createInstance() 方法用于创建 Foo 实例:

public class InstanceFooFactory {

    public Foo createInstance() {
        return new Foo();
    }
}

关键来了,Spring 配置要分两步走:

  1. 先把工厂类注册成 Bean ✅
  2. 再定义目标 Bean,通过 factory-bean 指定工厂实例,factory-method 指定创建方法

对应的 XML 配置如下:

<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
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 工厂类本身也是一个 Bean -->
    <bean id="instanceFooFactory"
          class="com.example.factory.InstanceFooFactory" />

    <!-- 使用工厂方法创建 Foo -->
    <bean id="foo"
          factory-bean="instanceFooFactory"
          factory-method="createInstance" />

</beans>

测试一下是否成功注入:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/instance-config.xml")
public class InstanceFooFactoryIntegrationTest {

    @Autowired
    private Foo foo;
    
    @Test
    public void givenValidInstanceFactoryConfig_whenCreateFooInstance_thenInstanceIsNotNull() {
        assertNotNull(foo); // ✅ 成功创建
    }
}

2.2 带参创建

更常见的情况是,目标对象需要传参。比如我们有一个 Bar 类,构造时需要一个 name

public class Bar {

    private String name;

    public Bar(String name) {
        this.name = name;
    }

    // getter / setter 省略
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

对应的工厂类 InstanceBarFactory 提供一个带参的 createInstance() 方法:

public class InstanceBarFactory {

    public Bar createInstance(String name) {
        return new Bar(name);
    }
}

XML 配置中使用 <constructor-arg> 传参(名字有点误导,其实这里不是构造器参数,而是工厂方法参数)⚠️:

<beans ...>

    <bean id="instanceBarFactory"
          class="com.example.factory.InstanceBarFactory" />

    <bean id="bar"
          factory-bean="instanceBarFactory"
          factory-method="createInstance">
        <constructor-arg value="someName" />
    </bean>

</beans>

测试验证参数是否正确传递:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/instance-bar-config.xml")
public class InstanceBarFactoryIntegrationTest {

    @Autowired
    private Bar instance;
    
    @Test
    public void givenValidInstanceFactoryConfig_whenCreateInstance_thenNameIsCorrect() {
        assertNotNull(instance);
        assertEquals("someName", instance.getName()); // ✅ 参数生效
    }
}

⚠️ 踩坑提示:constructor-arg 这个标签名容易让人误解,其实它在这里是给工厂方法传参,而不是调用目标类的构造函数。如果方法参数多,还可以用 indextype 区分。


3. 静态工厂方法(Static Factory Method)

静态工厂方法不需要先创建工厂实例,直接通过类名调用静态方法即可创建 Bean。适用于工具类、单例工厂等场景。

✅ 优势:配置更简洁,无需提前注册工厂 Bean
❌ 缺点:难以管理工厂状态,不利于测试和扩展,建议优先使用实例工厂

3.1 无参创建

我们沿用 Foo 类,创建一个单例工厂 SingletonFooFactory

public class SingletonFooFactory {

    private static final Foo INSTANCE = new Foo();
    
    public static Foo createInstance() {
        return INSTANCE;
    }
}

配置更简单了,只需要指定 classfactory-method 即可:

<beans ...>

    <bean id="foo"
          class="com.example.factory.SingletonFooFactory"
          factory-method="createInstance" />

</beans>

测试注入是否成功:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/static-foo-config.xml")
public class SingletonFooFactoryIntegrationTest {

    @Autowired
    private Foo singleton;
    
    @Test
    public void givenValidStaticFactoryConfig_whenCreateInstance_thenInstanceIsNotNull() {
        assertNotNull(singleton); // ✅
    }
}

3.2 带参创建

虽然静态工厂不推荐修改内部状态,但 Spring 依然支持传参。例如我们想动态设置 Bar 的名字:

public class SingletonBarFactory {

    private static final Bar INSTANCE = new Bar("unnamed");
    
    public static Bar createInstance(String name) {
        INSTANCE.setName(name); // ⚠️ 修改单例状态!危险操作
        return INSTANCE;
    }
}

配置传参方式与实例工厂一致:

<beans ...>

    <bean id="bar"
          class="com.example.factory.SingletonBarFactory"
          factory-method="createInstance">
        <constructor-arg value="someName" />
    </bean>

</beans>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/static-bar-config.xml")
public class SingletonBarFactoryIntegrationTest {

    @Autowired
    private Bar instance;
    
    @Test
    public void givenValidStaticFactoryConfig_whenCreateInstance_thenNameIsCorrect() {
        assertNotNull(instance);
        assertEquals("someName", instance.getName()); // ✅
    }
}

⚠️ 踩坑警告:上面这种写法虽然能跑通,但存在严重问题 —— 多个地方调用会覆盖单例状态,导致不可预测行为。生产环境应避免此类设计,建议改为每次返回新实例,或改用实例工厂。


4. 总结

方式 是否需要工厂 Bean 适用场景 推荐度
实例工厂方法 ✅ 需要 复杂创建逻辑、依赖注入、可测试性强 ✅✅✅ 强烈推荐
静态工厂方法 ❌ 不需要 遗留系统集成、工具类、单例工厂 ✅ 仅限必要时使用

核心要点回顾

  • 工厂方法适合封装复杂创建逻辑,提升配置灵活性
  • 实例工厂更符合 Spring 的设计哲学,易于管理依赖
  • 静态工厂虽简洁,但状态管理风险高,慎用
  • <constructor-arg> 实际上传的是工厂方法参数,不是构造器参数,别被名字骗了

📌 最佳实践:优先使用实例工厂 + 构造注入,保持无状态、高内聚、易测试。

本文所有代码示例已整理至 GitHub:https://github.com/spring-example/factory-method-demo


原始标题:Creating Spring Beans Through Factory Methods