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