1. 概述

本文将介绍如何在 Spring 中动态注入 Bean

我们会先通过一个实际场景说明动态注入的应用价值,然后给出两种不同的实现方案。对于有经验的开发者来说,这属于“进阶用法”,但在某些复杂业务场景下非常实用,能避免写一堆 if-else 判断实现类。

2. 动态注入的典型场景

动态注入的核心价值在于:运行时根据条件切换不同的 Bean 实现
这在多区域、多策略、多渠道等业务中特别常见。比如根据用户选择的国家,调用对应地区的服务逻辑。

举个例子:我们有一个全球服务器管理系统,需要根据不同国家判断服务器状态。

定义统一接口:

public interface RegionService {
    boolean isServerActive(int serverId);
    String getISOCountryCode();
}

两个具体实现类,分别代表英国和美国的服务逻辑:

@Service("GBregionService")
public class GBRegionService implements RegionService {
    @Override
    public boolean isServerActive(int serverId) {
        return false;
    }

    @Override
    public String getISOCountryCode() {
        return "GB";
    }
}
@Service("USregionService")
public class USRegionService implements RegionService {
    @Override
    public boolean isServerActive(int serverId) {
        return true;
    }

    @Override
    public String getISOCountryCode() {
        return "US";
    }
}

现在的问题是:前端传入 isoCountryCode,后端如何自动选择对应的 RegionService
传统做法是写一堆 if ("US".equals(code)),耦合度高还不好维护。
✅ 正确姿势:利用 Spring 的动态注入能力,让容器帮我们选实现类。

3. 使用 BeanFactory 实现动态注入

BeanFactory 是 Spring 容器的底层接口,提供了按名称或类型获取 Bean 的能力。它本身也是一个 Bean,可以直接注入。

示例代码:

@Service
public class BeanFactoryDynamicAutowireService {
    private static final String SERVICE_NAME_SUFFIX = "regionService";
    private final BeanFactory beanFactory;

    @Autowired
    public BeanFactoryDynamicAutowireService(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public boolean isServerActive(String isoCountryCode, int serverId) {
        RegionService service = beanFactory.getBean(getRegionServiceBeanName(isoCountryCode), 
          RegionService.class);
        return service.isServerActive(serverId);
    }

    private String getRegionServiceBeanName(String isoCountryCode) {
        return isoCountryCode + SERVICE_NAME_SUFFIX;
    }
}

关键点说明:

  • ✅ 利用 getBean(String name, Class<T> type) 精准获取目标 Bean
  • ✅ Bean 名称与国家码绑定(如 GBregionService
  • ⚠️ 缺点:侵入了 Spring 底层 API,代码耦合容器,不够“纯粹”的 DI 风格

虽然能跑通,但总觉得有点“糙”。有没有更符合 Spring 设计哲学的方式?

4. 使用接口 + Map 实现依赖注入

Spring 有个隐藏技能:当注入一个接口的 List 时,容器会自动把所有实现类都塞进来。我们可以借此构建一个“实现类注册表”。

更优雅的写法:

@Service
public class CustomMapFromListDynamicAutowireService {
    private final Map<String, RegionService> servicesByCountryCode;

    @Autowired
    public CustomMapFromListDynamicAutowireService(List<RegionService> regionServices) {
        servicesByCountryCode = regionServices.stream()
                .collect(Collectors.toMap(
                    RegionService::getISOCountryCode, 
                    Function.identity()
                ));
    }

    public boolean isServerActive(String isoCountryCode, int serverId) {
        RegionService service = servicesByCountryCode.get(isoCountryCode);
        if (service == null) {
            throw new IllegalArgumentException("No service found for country: " + isoCountryCode);
        }
        return service.isServerActive(serverId);
    }
}

优势分析:

  • ✅ 完全基于标准 DI,无容器 API 依赖
  • ✅ 构造时完成映射初始化,运行时查表即可,性能好
  • ✅ 扩展性强:新增国家只需加个实现类,无需改核心逻辑
  • ✅ 代码更“Spring 风格”,符合约定优于配置原则

踩坑提醒:

  • ❌ 注意 @Service 注解必须写,否则不会被扫描进容器
  • getISOCountryCode() 返回值必须唯一,否则 Map 构建会冲突
  • ✅ 建议加空值校验,避免 NullPointerException

5. 总结

本文介绍了两种 Spring 动态注入 Bean 的方案:

方式 优点 缺点 推荐指数
BeanFactory 简单直接,灵活 耦合容器API ⭐⭐⭐
List → Map 映射 解耦、可扩展、符合DI理念 需要额外初始化 ⭐⭐⭐⭐⭐

推荐优先使用第二种方式 —— 它更符合 Spring 的设计思想,代码更干净,维护成本更低。

示例代码已上传至 GitHub:https://github.com/tech-tutorial/spring-di-dynamic-demo


原始标题:How to Dynamically Autowire a Bean in Spring