← 返回题目列表

抽象工厂模式和 Spring 依赖注入如何结合?

高频 中等 第 5 / 25 题 更新于 2026/07/28
抽象工厂模式Spring依赖注入配置切换

简化版

Spring 可以负责装配具体抽象工厂,业务代码只依赖抽象工厂接口。通过配置、Profile、条件装配或 Bean 名称选择具体工厂,就能在启动期切换一整套产品族。

详细版

抽象工厂接口可以这样定义:

interface CloudFactory {
    StorageClient createStorage();
    QueueClient createQueue();
}

不同厂商提供不同实现:

@Component
class AliCloudFactory implements CloudFactory {
    public StorageClient createStorage() {
        return new AliStorageClient();
    }

    public QueueClient createQueue() {
        return new AliQueueClient();
    }
}

业务服务只注入 CloudFactory

class FileService {
    private final CloudFactory cloudFactory;

    FileService(CloudFactory cloudFactory) {
        this.cloudFactory = cloudFactory;
    }
}

具体使用哪套工厂,可以通过 @Profile@ConditionalOnProperty、配置类或 @Qualifier 决定。这样 Spring 负责选择和生命周期管理,抽象工厂负责表达产品族边界。

完整版教学

一、Spring 可以管理工厂本身

抽象工厂不一定要手动 new。在 Spring 中,具体工厂可以是普通 Bean,工厂依赖的配置、连接池、认证组件也可以通过构造器注入。

这样能把对象创建逻辑和容器生命周期结合起来,减少手动组装错误。

二、用配置切换产品族

常见做法是根据环境选择具体工厂:

@Bean
CloudFactory cloudFactory(CloudProperties props) {
    return switch (props.provider()) {
        case "ali" -> new AliCloudFactory(props);
        case "aws" -> new AwsCloudFactory(props);
        default -> throw new IllegalArgumentException("unknown provider");
    };
}

这段选择逻辑集中在配置层,业务层依然只依赖 CloudFactory

三、多个工厂并存时要显式选择

如果系统同时接入多个厂商,可能需要注入 Map<String, CloudFactory>,根据租户或请求动态选择。此时 Spring 提供注册表能力,抽象工厂提供成套创建能力。

要注意动态选择可能涉及缓存、线程安全、租户隔离和凭证刷新,不能只看模式结构。

四、不要滥用 ApplicationContext

不建议在业务代码里到处调用 applicationContext.getBean() 获取具体工厂,这会隐藏依赖关系。更好的方式是显式注入抽象工厂、工厂注册表或路由器。

依赖显式,测试替换也更容易。

五、Spring 负责选工厂,工厂负责守住产品族

启动期单一云厂商可由 Profile 或条件装配选中一个 CloudFactory;多租户场景则可注入 Map<String,CloudFactory>,运行时路由到某一族。Spring 的职责是装配与生命周期,抽象工厂的职责是成套创建。

ApplicationContext
  ali -> AliCloudFactory -> AliStorage + AliQueue
  aws -> AwsCloudFactory -> AwsStorage + AwsQueue
启动期单选: @ConditionalOnProperty
运行期多选: Map<String, CloudFactory>
TenantRouter 只选择工厂
业务从选定工厂取得同族产品

矩阵的每一行是一族兼容产品,每一列是一个产品等级。客户端选择一行对应的具体工厂,再从该工厂取得各列产品;如果允许调用方绕过工厂分别选择每个具体类,产品族一致性的结构性保证就消失了。

六、二维扩展成本与模式边界

评审维度本题结论
产品族不变量一次业务上下文内使用同一个 CloudFactory 创建所有相关客户端。
新增一行(产品族)新增具体 Factory/Products,并以 Bean 形式注册。
新增一列(产品等级)修改 CloudFactory 接口和所有 Bean 实现,再调整契约测试。
不应由工厂承担在业务代码中随处调用 ApplicationContext 查具体 Bean。
退回更轻方案的条件只有一组固定产品可直接注入产品;无需动态创建时不强行保留工厂层。

抽象工厂对所有变化并非都开放。它刻意固定列集合,以换取整行切换的一致性;如果系统最常发生的是新增列,工厂接口和所有行实现会频繁联动,此时应拆分工厂边界或选择组合方式。

七、落地验收清单

  1. 为每个具体工厂跑同一组契约测试,确认每个创建方法都返回正确产品等级。
  2. 给产品暴露 familyId() 仅用于测试,校验同一工厂创建的所有产品 familyId 一致。
  3. 加入一个假产品族,记录是否只新增一行实现和装配配置,而未修改旧工厂。
  4. 模拟新增一个产品等级,评估抽象接口和全部具体工厂的真实修改成本。
  5. 检查公共产品接口是否出现 AWS、MySQL、Windows 等具体实现词汇。
  6. 检查具体工厂是否只做组装与创建,没有吞入完整业务流程。
  7. 若产品持有连接或线程池,明确由工厂、容器还是调用方负责关闭。
  8. 若运行期按租户切换工厂,验证路由缓存、凭证刷新和租户隔离。

记忆钩子:容器决定注入哪一行,抽象工厂保证这一行里的列不混搭。

八、常见误区与追问

  • 误区:Spring Bean 名称自动等于业务 provider key。 需要明确命名、Qualifier 或自建映射规则。
  • 误区:多个 CloudFactory 注入时 Spring 会随机选一个。 无 Primary/Qualifier 时通常产生歧义并启动失败。
  • 误区:使用 ApplicationContext.getBean 更灵活。 它隐藏依赖并把业务层耦合到容器。
  • 追问:启动期切换用什么? 可用 Profile、条件属性或配置类选择唯一具体工厂。
  • 追问:运行期按租户怎么做? 注入工厂 Map/注册表,由显式路由器选择,处理凭证和隔离。
  • 追问:工厂 new 出来的对象 Spring 会自动销毁吗? 普通方法创建对象不会天然获得完整 Bean 生命周期,关闭责任需明确。

九、加强记忆

Spring 负责“把哪个具体工厂交给你”,抽象工厂负责“这个工厂能创建哪一整套产品”。两者结合时,配置切换放容器层,产品族创建放工厂层。