[054][核心模块]工厂模式的“Bean工具化”设计:从静态工具到Spring托管Bean的演进
在Spring Boot应用中,传统的静态工厂类虽然简单直接,但往往难以与依赖注入容器无缝协作。本文以CaptchaServiceFactory为例,探讨如何将工厂类“Bean工具化”——使其成为Spring上下文中的一等公民,同时保留单例模式的便捷性,并实现服务实现类的自动装配与动态查找。
一、传统工厂模式的局限
一个典型的验证码服务工厂可能如下设计:
public class CaptchaServiceFactory {
private static final Map<CaptchaCategory, CaptchaService> services = new EnumMap<>(...);
public static CaptchaService getService(CaptchaCategory category) {
... }
public static void registerService(...) {
... }
}
这种模式存在几个问题:
- 硬编码依赖:其他组件直接依赖静态方法,难以mock或替换。
- 扩展不灵活:添加新的
CaptchaService实现需要手动调用注册代码,无法利用Spring的自动发现机制。 - 测试困难:静态状态在多个测试用例间可能相互干扰。
- 与Spring生态割裂:无法享受
@Autowired、@Value、@Conditional等特性。
二、Bean工具化的核心思想
Bean工具化是指将传统工具类或工厂类改造为Spring管理的Bean,使其具备以下特征:
- 由Spring IoC容器负责实例化与生命周期管理。
- 通过依赖注入获取所需的协作组件(如各种
CaptchaService实现)。 - 对外提供简洁的非静态API,内部仍可保持单例状态。
- 支持条件装配、懒加载、代理等高级特性。
改造后的工厂不再是“一个静态方法集合”,而是一个服务定位器(Service Locator)模式的Spring Bean,既保留了按类型查找的核心功能,又融入了容器的自动装配能力。
三、CaptchaServiceFactory的代码分析
3.1 工厂类设计
public class CaptchaServiceFactory {
public static final CaptchaServiceFactory instance = new CaptchaServiceFactory();
protected EnumMap<CaptchaCategory, CaptchaService> services = new EnumMap<>(CaptchaCategory.class);
public CaptchaService findService(String categoryName) {
... }
public CaptchaService findService(CaptchaCategory category) {
... }
public Map<CaptchaCategory, CaptchaService> getServices() {
... }
public void setServices(Map<CaptchaCategory, CaptchaService> services) {
... }
}
关键点:
- 静态单例
instance:保持全局唯一实例,方便非Spring环境(如传统Servlet、单元测试)直接使用。 setServices:允许外部批量注入服务映射,核心扩展点。findService:根据分类枚举或名称查找,若找不到则抛出业务异常。
该设计兼顾了单例模式的便捷性与Setter注入的可配置性。
3.2 Spring自动配置类中的装配逻辑
CaptchaConfiguration负责将CaptchaServiceFactory转化为Spring Bean,并自动收集所有CaptchaService实现:
@Bean
@ConditionalOnMissingBean
CaptchaServiceFactory captchaServiceFactory(ObjectProvider<CaptchaService> providers) {
Map<CaptchaCategory, CaptchaService> services =
providers.stream().collect(Collectors.toMap(CaptchaService::getCategory, m -> m));
log.debug("[CAPTCHA-CORE] 工厂'CaptchaServiceFactory'注入实例:{}", services);
CaptchaServiceFactory.instance.setServices(services);
return CaptchaServiceFactory.instance;
}
装配流程解析:
ObjectProvider<CaptchaService>:Spring会注入容器中所有实现了CaptchaService接口的Bean(包括后续通过@Bean或组件扫描添加的)。- 转换为EnumMap:调用每个
CaptchaService的getCategory()方法作为Key,服务本身作为Value,构建映射。 - 填充静态单例:将映射设置到
CaptchaServiceFactory.instance中,保证静态实例与Spring Bean内部状态完全一致。 - 返回单例对象:虽然方法返回的是
CaptchaServiceFactory.instance,但由于该对象是静态单例,全局唯一,因此Spring容器中实际存在的也是同一个对象。
这样就实现了:Spring容器管理的Bean与静态单例指向同一实例。既可以通过@Autowired注入工厂,也可以通过CaptchaServiceFactory.instance静态访问,两者等价。
3.3 为什么需要同时提供静态实例和Spring Bean?
- 非Spring模块:项目中可能遗留Servlet、过滤器或非Spring托管的组件,它们可以通过静态实例快速获取工厂。
- 框架代码的兼容性:
CaptchaRequestFilter通过构造方法注入CaptchaServiceFactory,这是标准的Spring风格;而其他无法使用DI的地方可直接使用instance。 - 简化测试:测试类中既可用
@MockBean覆盖,也可直接调用静态方法设置mock服务。
四、Bean工具化的优势体现
4.1 自动化服务注册
开发者只需编写一个新的CaptchaService实现类并用@Component或@Service标注,Spring会自动将其注册到工厂中,无需任何手工编码。例如:
@Component
public class TianaiBehaviorCaptchaService implements CaptchaService {
@Override
public CaptchaCategory getCategory() {
return CaptchaCategory.TIANAI_BEHAVIOR; }
// ... 其他方法
}
该Bean会被ObjectProvider收集,自动进入工厂的services Map。
4.2 条件装配能力
@ConditionalOnMissingBean保证用户可以自定义CaptchaServiceFactory Bean来覆盖默认行为。CaptchaProperties、HutoolCaptchaProperties等配置类可以灵活调整不同验证码引擎的参数。
4.3 过滤器中的无缝集成
CaptchaRequestFilter通过构造函数注入工厂:
FilterRegistrationBean<CaptchaRequestFilter> captchaRequestFilterRegistration(
CaptchaServiceFactory captchaServiceFactory, CaptchaProperties properties) {
CaptchaRequestFilter filter = new CaptchaRequestFilter(captchaServiceFactory);
// ...
}
由于工厂已经是Spring Bean,过滤器可以自然地获取它。对比传统方式(CaptchaServiceFactory.getService(...)),这种方式更易于单元测试(可注入mock工厂)。
4.4 线程安全与不可变视图
getServices()返回Collections.unmodifiableMap,避免外部意外修改服务映射。setServices内部使用putAll,且仅在启动阶段调用一次,运行时映射是稳定的。
五、总结:从工具到服务的演进
| 特性 | 传统静态工厂 | Bean工具化后的工厂 |
|---|---|---|
| 服务注册方式 | 手动调用静态注册方法 | Spring自动收集CaptchaService Bean |
| 依赖获取 | 直接调用静态方法,紧耦合 | 通过@Autowired或构造器注入,解耦 |
| 可扩展性 | 需修改工厂类或额外初始化代码 | 新增实现类即自动生效,符合开闭原则 |
| 测试友好度 | 需用PowerMock或重置静态状态 | 可注入Mock对象,使用标准Mockito |
| 与Spring生态集成 | 差,无法利用AOP、条件装配等 | 完美集成,享受@ConfigurationProperties等 |
CaptchaServiceFactory通过“静态单例 + Spring Bean代理”的巧妙设计,既保留了传统工具类的简洁访问方式,又无缝融入了Spring的依赖注入容器。这种Bean工具化模式非常适用于封装第三方组件或内部基础设施类,尤其适用于需要提供查找服务(Service Locator)而又希望利用Spring自动装配能力的场景。
在实际项目中,我们可以将类似的设计思想推广到其他工厂类、策略管理器、注册中心等组件,实现“零配置、自动发现、松耦合”的优雅架构。