五维思考

学习要加,骄傲要减,机会要乘,懒惰要除。 http://www.5dthink.cn

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

SpringBoot 枚举驱动策略注册表

一、枚举驱动注册表版

核心设计思想

策略模式 + 枚举作为注册表 + 自动注入策略实例

新增加密算法:只需要 2 件事

  1. 新建类 XxxEncryptor extends AbstractEncryptor,实现加密解密逻辑
  2. 在枚举 EncryptTypeEnum 增加一个枚举常量,关联这个加密器 Bean
    ✅ 不需要修改工厂、不需要 if/switch、不需要改动已有业务代码

关键点:

  • 枚举不再只存编码描述,持有策略Bean名称 / Class,充当策略注册表
  • Spring 容器统一管理所有加密器实例
  • 工厂类从Spring上下文按枚举获取对应的策略实现
  • 完全开闭原则:对扩展开放,对修改关闭

整体结构

com.xxx.encrypt
├── AbstractEncryptor.java        // 策略顶层抽象
├── strategy
│   ├── AesEncryptor.java         // 策略实现1
│   ├── RsaEncryptor.java         // 策略实现2
│   └── XxxEncryptor.java         // 新增策略(后续扩展)
├── EncryptTypeEnum.java          // 【枚举注册表】
├── EncryptStrategyFactory.java   // 策略工厂,从Spring容器获取实例
└── EncryptService.java           // 业务调用入口

1. 抽象策略 AbstractEncryptor

所有加密器统一规范,定义加密/解密接口

public abstract class AbstractEncryptor {

    /**
     * 加密
     */
    public abstract String encrypt(String rawText);

    /**
     * 解密
     */
    public abstract String decrypt(String cipherText);

    /**
     * 获取当前加密器对应的类型编码,和枚举匹配(可选校验)
     */
    public abstract String getTypeCode();
}

实现类上加 @Component,交给Spring管理,单例Bean。

2. 枚举注册表 EncryptTypeEnum(核心)

枚举 = 策略注册表,每一个枚举项对应一个加密算法

字段:typeCode(编码)、desc(描述)、beanClass(策略实现类Class)

import lombok.Getter;

@Getter
public enum EncryptTypeEnum {

    AES("AES", "AES对称加密", AesEncryptor.class),
    RSA("RSA", "RSA非对称加密", RsaEncryptor.class);
    // 新增算法只需要在这里追加一行:
    // XXX("XXX", "xxx加密", XxxEncryptor.class);

    private final String typeCode;
    private final String desc;
    /** 绑定策略实现Class,工厂通过Class从Spring拿Bean */
    private final Class<? extends AbstractEncryptor> beanClass;

    EncryptTypeEnum(String typeCode, String desc, Class<? extends AbstractEncryptor> beanClass) {
        this.typeCode = typeCode;
        this.desc = desc;
        this.beanClass = beanClass;
    }

    /** 根据编码查找枚举 */
    public static EncryptTypeEnum getByCode(String typeCode) {
        for (EncryptTypeEnum e : EncryptTypeEnum.values()) {
            if (e.getTypeCode().equals(typeCode)) {
                return e;
            }
        }
        throw new IllegalArgumentException("不支持的加密类型:" + typeCode);
    }
}

3. 策略实现示例:AesEncryptor

import org.springframework.stereotype.Component;

@Component
public class AesEncryptor extends AbstractEncryptor {

    @Override
    public String encrypt(String rawText) {
        // AES加密逻辑
        return rawText + "_aes_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        // AES解密逻辑
        return cipherText.replace("_aes_encrypt", "");
    }

    @Override
    public String getTypeCode() {
        return EncryptTypeEnum.AES.getTypeCode();
    }
}
@Component
public class RsaEncryptor extends AbstractEncryptor {
    @Override
    public String encrypt(String rawText) {
        return rawText + "_rsa_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        return cipherText.replace("_rsa_encrypt", "");
    }

    @Override
    public String getTypeCode() {
        return EncryptTypeEnum.RSA.getTypeCode();
    }
}

4. 策略工厂 EncryptStrategyFactory

唯一的获取策略入口,从Spring上下文获取Bean,无任何if/switch

工厂不需要维护策略Map,枚举是注册表,Class是Bean的查找依据

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class EncryptStrategyFactory {

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 根据枚举获取加密器
     */
    public AbstractEncryptor getEncryptor(EncryptTypeEnum encryptTypeEnum) {
        // 直接通过枚举里保存的Class,从Spring容器拿Bean
        return applicationContext.getBean(encryptTypeEnum.getBeanClass());
    }

    /**
     * 根据typeCode字符串获取加密器(业务常用)
     */
    public AbstractEncryptor getEncryptor(String typeCode) {
        EncryptTypeEnum typeEnum = EncryptTypeEnum.getByCode(typeCode);
        return getEncryptor(typeEnum);
    }
}

原理:Spring容器已经实例化所有 @Component 的加密器实现;枚举保存Class,工厂只是做查找转发。

5. 业务服务 EncryptService

业务层调用示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EncryptService {

    @Autowired
    private EncryptStrategyFactory encryptStrategyFactory;

    public String encrypt(String typeCode, String content) {
        AbstractEncryptor encryptor = encryptStrategyFactory.getEncryptor(typeCode);
        return encryptor.encrypt(content);
    }

    public String decrypt(String typeCode, String content) {
        AbstractEncryptor encryptor = encryptStrategyFactory.getEncryptor(typeCode);
        return encryptor.decrypt(content);
    }
}

新增算法完整演示(只需要两步)

需求:新增SM4国密加密

  1. 新建实现类
@Component
public class Sm4Encryptor extends AbstractEncryptor {
    @Override
    public String encrypt(String rawText) {
        return rawText + "_sm4_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        return cipherText.replace("_sm4_encrypt", "");
    }

    @Override
    public String getTypeCode() {
        return EncryptTypeEnum.SM4.getTypeCode();
    }
}
  1. 在枚举 EncryptTypeEnum 添加一行常量
SM4("SM4", "SM4国密加密", Sm4Encryptor.class);

到此完成!工厂、Service、原有逻辑一行不改,直接可以使用:

encryptService.encrypt("SM4", "test");

调用方式

// 方式1:直接用枚举
AbstractEncryptor encryptor = factory.getEncryptor(EncryptTypeEnum.SM4);
String cipher = encryptor.encrypt("hello");

// 方式2:业务传字符串编码(http参数、数据库存储类型编码场景)
String cipher = encryptService.encrypt("SM4", "hello");

可选增强方案(生产常用)

方案A:启动自检(推荐)

项目启动时校验枚举中所有Class是否在Spring容器存在,防止漏写@Component:

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class EncryptCheckRunner implements CommandLineRunner {
    @Autowired
    private ApplicationContext ctx;

    @Override
    public void run(String... args) throws Exception {
        for (EncryptTypeEnum e : EncryptTypeEnum.values()) {
            // 启动时校验Bean存在
            ctx.getBean(e.getBeanClass());
        }
    }
}

启动直接报错,提前发现问题,而不是运行时才抛异常。

方案B:枚举保存Bean名称(备选)

如果同一个类有多个Bean实例,不存Class,存beanName字符串:

SM4("SM4", "SM4国密加密", "sm4Encryptor");

工厂:ctx.getBean(beanName, AbstractEncryptor.class)

方案C:注册Map缓存(减少ApplicationContext反复查找)

工厂内部做一个本地Map<Class<?>, AbstractEncryptor>缓存,容器启动一次性加载,减少getBean开销。

@Component
public class EncryptStrategyFactory {
    private final Map<Class<? extends AbstractEncryptor>, AbstractEncryptor> strategyMap;

    @Autowired
    public EncryptStrategyFactory(ApplicationContext ctx) {
        Map<String, AbstractEncryptor> beans = ctx.getBeansOfType(AbstractEncryptor.class);
        strategyMap = beans.values().stream()
                .collect(Collectors.toMap(AbstractEncryptor::getClass, v -> v));
    }

    public AbstractEncryptor getEncryptor(EncryptTypeEnum type) {
        return strategyMap.get(type.getBeanClass());
    }
}

优缺点分析

✅ 优点

  1. 开闭原则:新增策略只加实现类+枚举常量,工厂无改动
  2. 消灭大量 if/else / switch,代码干净
  3. 枚举集中管理所有算法清单,文档化,一眼看到全部支持类型
  4. Spring统一管理Bean,依赖注入(加密器内部可以注入配置、redis等)
  5. 类型安全,编码和实现强绑定,不容易传错类型

⚠️ 缺点&坑点

  1. 枚举编译期固定:不能动态新增算法(运行时不重启新增算法不行,适合算法集合固定场景)>

如果需要运行时动态注册策略,这个方案不适用,要改用内存Map注册表。

  1. 枚举和实现类存在双向绑定,需要启动自检,防止漏写@Component
  2. 不能直接使用抽象类,所有实现必须交给Spring容器实例化,不能new

适用场景

  • 算法集合预先确定、不会运行时动态新增
  • 加解密、解析器、消息处理器、报表导出器、支付渠道处理器等>

比如:文件解析策略(excel/csv/pdf)、消息发送策略(短信/邮件/钉钉)都可以套用这套模板


二、注解驱动注册器版

不用手动改枚举,用注解自动扫描注册
改造目标:注解驱动策略注册器(不再手动维护枚举!)

原始方案痛点:新增策略必须手动修改枚举,容易忘记、枚举和实现类耦合。
改造后:自定义注解标记策略实现类 → Spring启动自动扫描所有带注解的加密器 → 自动构建注册表
✅ 新增算法:只需要写 XxxEncryptor extends AbstractEncryptor + 在类上加 @EncryptStrategy(code = "SM4", desc = "国密SM4加密")
✅ 不用修改任何枚举、不用修改工厂、不用改注册表

核心变化:

  1. 去掉手动维护的 EncryptTypeEnum;
  2. 新增注解 @EncryptStrategy,用来定义策略编码、描述;
  3. 策略注册器 EncryptStrategyRegistry:Spring启动时扫描所有实现 AbstractEncryptor 且带注解的Bean,构建内存注册表;
  4. 工厂直接从注册器获取策略实例;
  5. 保留原有抽象策略,业务调用方式几乎不变。

整体包结构

com.xxx.encrypt
├── AbstractEncryptor.java            // 策略抽象父类(不变)
├── annotation
│   └── EncryptStrategy.java          // 自定义策略注解
├── registry
│   └── EncryptStrategyRegistry.java  // 【核心】策略注册器,自动扫描、维护注册表
├── EncryptStrategyFactory.java       // 策略工厂,对外获取策略
├── EncryptService.java               // 业务入口
└── strategy
    ├── AesEncryptor.java
    ├── RsaEncryptor.java
    └── Sm4Encryptor.java(新增只需要写这个+加注解)

1. 自定义注解 @EncryptStrategy

标记策略实现类,定义策略唯一编码、描述

package com.xxx.encrypt.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptStrategy {
    /** 策略唯一编码,业务用来选择算法,全局不能重复 */
    String code();
    /** 策略描述 */
    String desc() default "";
}

2. 抽象策略 AbstractEncryptor(基本不变,删掉getTypeCode可选)

package com.xxx.encrypt;

public abstract class AbstractEncryptor {
    public abstract String encrypt(String rawText);
    public abstract String decrypt(String cipherText);
}

3. 策略实现示例(加注解 + @Component)

AesEncryptor

package com.xxx.encrypt.strategy;

import com.xxx.encrypt.AbstractEncryptor;
import com.xxx.encrypt.annotation.EncryptStrategy;
import org.springframework.stereotype.Component;

@Component
@EncryptStrategy(code = "AES", desc = "AES对称加密")
public class AesEncryptor extends AbstractEncryptor {
    @Override
    public String encrypt(String rawText) {
        return rawText + "_aes_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        return cipherText.replace("_aes_encrypt", "");
    }
}

RsaEncryptor

@Component
@EncryptStrategy(code = "RSA", desc = "RSA非对称加密")
public class RsaEncryptor extends AbstractEncryptor {
    @Override
    public String encrypt(String rawText) {
        return rawText + "_rsa_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        return cipherText.replace("_rsa_encrypt", "");
    }
}

✅ 新增SM4示例(只写这个类+注解即可,无其他改动)

@Component
@EncryptStrategy(code = "SM4", desc = "国密SM4加密")
public class Sm4Encryptor extends AbstractEncryptor {
    @Override
    public String encrypt(String rawText) {
        return rawText + "_sm4_encrypt";
    }

    @Override
    public String decrypt(String cipherText) {
        return cipherText.replace("_sm4_encrypt", "");
    }
}

4. 策略注册器 EncryptStrategyRegistry【核心】

Spring容器初始化完成后,自动收集所有加密策略,内存维护注册表。

启动时自动校验:编码不能重复,重复直接启动失败,提前发现冲突。

package com.xxx.encrypt.registry;

import com.xxx.encrypt.AbstractEncryptor;
import com.xxx.encrypt.annotation.EncryptStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class EncryptStrategyRegistry {

    /** key:策略code,value:加密器实例 */
    private final Map<String, AbstractEncryptor> strategyMap = new ConcurrentHashMap<>();

    @Autowired
    private Map<String, AbstractEncryptor> allEncryptorBeans;

    @PostConstruct
    public void initRegistry() {
        // 遍历所有AbstractEncryptor类型Bean
        for (AbstractEncryptor encryptor : allEncryptorBeans.values()) {
            Class<?> clazz = encryptor.getClass();
            EncryptStrategy annotation = clazz.getAnnotation(EncryptStrategy.class);
            if (annotation == null) {
                // 可选:如果有抽象父类/临时实现类不加注解,直接跳过
                log.warn("加密器 {} 未标记@EncryptStrategy注解,跳过注册", clazz.getName());
                continue;
            }
            String code = annotation.code();
            if (strategyMap.containsKey(code)) {
                throw new IllegalArgumentException("策略编码【" + code + "】重复!类:" + clazz.getName());
            }
            strategyMap.put(code, encryptor);
            log.info("注册加密策略 code={}, desc={}, class={}", code, annotation.desc(), clazz.getSimpleName());
        }
    }

    /** 根据编码获取策略 */
    public AbstractEncryptor getStrategy(String code) {
        AbstractEncryptor encryptor = strategyMap.get(code);
        if (encryptor == null) {
            throw new IllegalArgumentException("不存在的加密策略:" + code);
        }
        return encryptor;
    }

    /** 获取全部策略编码清单,用于接口返回下拉列表 */
    public Map<String, String> getAllStrategyDesc() {
        return strategyMap.entrySet().stream()
                .collect(ConcurrentHashMap::new, (map, entry) -> {
                    Class<?> clazz = entry.getValue().getClass();
                    EncryptStrategy anno = clazz.getAnnotation(EncryptStrategy.class);
                    map.put(entry.getKey(), anno.desc());
                }, ConcurrentHashMap::putAll);
    }
}

原理:Map<String, AbstractEncryptor> allEncryptorBeans 是Spring自动注入,key是beanName,value是所有实现AbstractEncryptor的Bean。@PostConstruct 在Bean创建完成后一次性扫描注册。

5. 策略工厂 EncryptStrategyFactory(轻量化包装,对外统一入口)

工厂可以很薄,直接委托注册器,业务层依然面向工厂调用,方便后续扩展拦截、埋点。

package com.xxx.encrypt;

import com.xxx.encrypt.registry.EncryptStrategyRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class EncryptStrategyFactory {

    @Autowired
    private EncryptStrategyRegistry strategyRegistry;

    public AbstractEncryptor getEncryptor(String code) {
        return strategyRegistry.getStrategy(code);
    }
}

6. 业务服务 EncryptService

业务调用几乎和老版本完全一致

package com.xxx.encrypt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EncryptService {

    @Autowired
    private EncryptStrategyFactory encryptStrategyFactory;

    public String encrypt(String typeCode, String content) {
        AbstractEncryptor encryptor = encryptStrategyFactory.getEncryptor(typeCode);
        return encryptor.encrypt(content);
    }

    public String decrypt(String typeCode, String content) {
        AbstractEncryptor encryptor = encryptStrategyFactory.getEncryptor(typeCode);
        return encryptor.decrypt(content);
    }
}

7. 业务调用示例

// 加密
String cipher = encryptService.encrypt("SM4", "hello world");
// 解密
String raw = encryptService.decrypt("SM4", cipher);

// 获取全部策略列表(给前端下拉框)
Map<String, String> allStrategy = registry.getAllStrategyDesc();

✅ 新增策略完整流程

  1. 新建类 XxxEncryptor extends AbstractEncryptor
  2. 类上加 @Component 和 @EncryptStrategy(code="XXX", desc="xxx算法")
  3. 重启SpringBoot,自动注册到注册表>

不需要修改工厂、注册表、枚举,零侵入原有代码。


关键特性 & 坑点说明

✅ 优势对比枚举版

  1. 消除枚举维护成本,新增策略只关心实现类本身;
  2. 启动阶段自动校验编码唯一性,提前发现冲突;
  3. getAllStrategyDesc() 可以直接输出全部支持算法,用于接口文档、前端下拉;
  4. 策略实现类可以自由放在任意包,只要被Spring扫描;
  5. 策略Bean可以正常使用依赖注入(注入配置、Redis、其他Service)。

⚠️ 坑点

  1. 必须加 @Component:否则不会被Spring实例化,不会被注入到 allEncryptorBeans;
  2. 注解必须打在实际Bean类上,不能打在父类;如果是CGLIB代理类,注解读取不受影响;
  3. 编码code全局唯一;启动时校验,运行期不会再变(静态策略集);
  4. 如果策略类不在Spring扫描包范围,会扫描不到,需要调整 @ComponentScan;
  5. 依然不支持运行时动态新增策略(新增策略还是要写代码+重启服务);如果需要运行时动态注册,可以给注册器开放 register() 方法。

🧩 可选增强项

增强1:统一策略元数据对象(StrategyMeta)

把 code、desc、class封装成对象,注册表不再只存实例,同时保存元信息:

@Data
public class StrategyMeta {
    private String code;
    private String desc;
    private Class<?> clazz;
    private AbstractEncryptor instance;
}

注册表改为 Map<String, StrategyMeta>,方便统一管理元信息。

增强2:支持SpringBoot Starter自动装配(大型项目)

抽成starter,不需要手动@ComponentScan,自动启用策略注册。

增强3:增加策略优先级、是否启用开关

注解扩展属性:boolean enable() default true,int order() default 0;注册时过滤掉 enable=false 的策略。

@EncryptStrategy(code = "SM4", desc = "国密SM4", enable = true, order = 1)

增强4:AOP埋点,统一统计加密耗时、异常监控

在工厂层加AOP,所有加密解密统一日志、监控,策略实现只关心业务逻辑。

增强5:泛型版本

把 AbstractEncryptor<I,O> 改造为泛型策略,支持入参出参类型不一样场景。

对比两种方案选型建议

  1. 枚举注册表版:策略集合极少、几乎不新增,希望编译期强类型,代码可读性强;缺点每次新增要改枚举。
  2. 注解驱动注册器版:策略较多、经常新增,希望遵循插件化开发风格,新增策略对原有代码零改动;缺点运行期才收集元信息。

三、泛型版|注解驱动策略注册表

目标:

  • 抽象策略改为泛型 AbstractStrategy<I,O>,入参I、出参O可以不一样
  • 沿用注解 @Strategy 自动扫描注册,不再绑定加密场景,通用策略框架
  • 原有加密场景可以无缝迁移;同时支持任意策略:解析器、转换器、处理器等
  • 保留:启动自动扫描、编码重复校验、注册表、工厂模式

示例业务场景:

  1. 加密策略:AbstractStrategy<String,String> 输入明文String,输出密文String
  2. 文件解析:AbstractStrategy<MultipartFile, List<RowData>> 输入文件,输出行数据列表
  3. 消息推送:AbstractStrategy<MessageDTO, Boolean> 输入消息对象,返回是否发送成功

包结构

com.xxx.strategy
├── annotation
│   └── Strategy.java                // 策略注解
├── registry
│   └── StrategyRegistry.java        // 策略注册器(核心)
├── AbstractStrategy.java            // ✅ 泛型抽象策略
├── StrategyFactory.java             // 策略工厂
├── StrategyService.java             // 业务调用门面
└── impl
    ├── AesEncryptStrategy.java
    ├── Sm4EncryptStrategy.java
    └── FileParseStrategy.java       // 不同泛型示例

1. 自定义注解 @Strategy

package com.xxx.strategy.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Strategy {
    /** 策略唯一编码 */
    String code();
    /** 策略描述 */
    String desc() default "";
    /** 是否启用 */
    boolean enable() default true;
}

2. 泛型抽象父类 AbstractStrategy<I,O>

I:输入类型;O:输出类型

package com.xxx.strategy;

public interface AbstractStrategy<I, O> {
    /**
     * 策略执行方法
     * @param input 入参
     * @return 输出
     */
    O execute(I input);
}

说明:这里改用接口而不是抽象类,泛型接口在Spring里注入 Map<String, AbstractStrategy<?,?>> 更顺滑;如果你坚持用abstract class,我也可以切换回去。

如果需要异常统一封装,可以加默认方法:

public interface AbstractStrategy<I, O> {
    default O execute(I input) {
        try {
            return doExecute(input);
        } catch (Exception e) {
            throw new StrategyExecuteException("策略执行异常", e);
        }
    }
    O doExecute(I input);
}

配套自定义异常 StrategyExecuteException,方便上层捕获。

3. 策略元数据 StrategyMeta(保存泛型信息 + 实例)

关键:因为Java泛型擦除,运行时拿不到接口泛型I/O,两种方案:
方案A(推荐,简单):注解不存泛型,使用者调用时自己保证类型匹配;类型不匹配抛类型转换异常。适合大多数业务。
方案B(增强):在注解增加 inputClass()、outputClass() 属性,显式声明入参出参Class,注册时校验类型安全。下面直接用方案B,强类型校验。

StrategyMeta.java

package com.xxx.strategy.registry;

import com.xxx.strategy.AbstractStrategy;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class StrategyMeta {
    /** 策略编码 */
    private String code;
    /** 描述 */
    private String desc;
    /** 是否启用 */
    private boolean enable;
    /** 入参类型 */
    private Class<?> inputClass;
    /** 出参类型 */
    private Class<?> outputClass;
    /** 策略实例Bean */
    private AbstractStrategy<?, ?> instance;
}

修改注解 @Strategy,增加入参出参Class

package com.xxx.strategy.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Strategy {
    String code();
    String desc() default "";
    boolean enable() default true;
    /** 策略入参Class */
    Class<?> inputClass();
    /** 策略出参Class */
    Class<?> outputClass();
}

4. 策略实现示例

示例1:AES加密策略 I=String, O=String

package com.xxx.strategy.impl;

import com.xxx.strategy.AbstractStrategy;
import com.xxx.strategy.annotation.Strategy;
import org.springframework.stereotype.Component;

@Component
@Strategy(
        code = "AES",
        desc = "AES字符串加密",
        inputClass = String.class,
        outputClass = String.class
)
public class AesEncryptStrategy implements AbstractStrategy<String, String> {
    @Override
    public String execute(String input) {
        // 加密逻辑
        return input + "_aes_encrypted";
    }
}

示例2:文件解析策略 I=MultipartFile, O=List

package com.xxx.strategy.impl;

import com.xxx.strategy.AbstractStrategy;
import com.xxx.strategy.annotation.Strategy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

@Component
@Strategy(
        code = "CSV_PARSE",
        desc = "CSV文件解析",
        inputClass = MultipartFile.class,
        outputClass = List.class
)
public class CsvParseStrategy implements AbstractStrategy<MultipartFile, List<String>> {
    @Override
    public List<String> execute(MultipartFile file) {
        // 解析文件返回行列表
        return List.of("line1","line2");
    }
}

5. 策略注册器 StrategyRegistry【核心】

Spring启动后收集所有策略,构建 Map<String, StrategyMeta>,校验编码重复、启用标记。

package com.xxx.strategy.registry;

import com.xxx.strategy.AbstractStrategy;
import com.xxx.strategy.annotation.Strategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class StrategyRegistry {

    private final Map<String, StrategyMeta> metaMap = new ConcurrentHashMap<>();

    // Spring自动注入所有实现AbstractStrategy的Bean
    @Autowired
    private Map<String, AbstractStrategy<?, ?>> allStrategyBeans;

    @PostConstruct
    public void init() {
        for (AbstractStrategy<?, ?> strategy : allStrategyBeans.values()) {
            Class<?> clazz = strategy.getClass();
            Strategy anno = clazz.getAnnotation(Strategy.class);
            if (anno == null) {
                log.warn("策略类 {} 缺少@Strategy注解,跳过注册", clazz.getName());
                continue;
            }
            if (!anno.enable()) {
                log.info("策略 {} 已禁用,跳过注册", anno.code());
                continue;
            }
            String code = anno.code();
            if (metaMap.containsKey(code)) {
                throw new IllegalArgumentException("策略编码【" + code + "】重复冲突!类:" + clazz.getName());
            }
            StrategyMeta meta = StrategyMeta.builder()
                    .code(code)
                    .desc(anno.desc())
                    .enable(anno.enable())
                    .inputClass(anno.inputClass())
                    .outputClass(anno.outputClass())
                    .instance(strategy)
                    .build();
            metaMap.put(code, meta);
            log.info("注册策略 code={}, desc={}, input={}, output={}, class={}",
                    code, anno.desc(), anno.inputClass().getSimpleName(), anno.outputClass().getSimpleName(), clazz.getSimpleName());
        }
    }

    /** 获取策略元信息 */
    public StrategyMeta getMeta(String code) {
        StrategyMeta meta = metaMap.get(code);
        if (meta == null) {
            throw new IllegalArgumentException("不存在策略编码:" + code);
        }
        return meta;
    }

    /** 获取策略实例,不做类型转换 */
    @SuppressWarnings("unchecked")
    public <I,O> AbstractStrategy<I,O> getStrategy(String code) {
        StrategyMeta meta = getMeta(code);
        return (AbstractStrategy<I,O>) meta.getInstance();
    }

    /** 获取全部策略元信息,用于前端下拉、文档 */
    public Map<String, StrategyMeta> getAllMeta(){
        return metaMap;
    }
}

6. 策略工厂 StrategyFactory

package com.xxx.strategy;

import com.xxx.strategy.registry.StrategyRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class StrategyFactory {

    @Autowired
    private StrategyRegistry strategyRegistry;

    public <I,O> AbstractStrategy<I,O> getStrategy(String code){
        return strategyRegistry.getStrategy(code);
    }
}

7. 业务门面 StrategyService

package com.xxx.strategy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StrategyService {

    @Autowired
    private StrategyFactory strategyFactory;

    public <I,O> O execute(String code, I input){
        AbstractStrategy<I,O> strategy = strategyFactory.getStrategy(code);
        return strategy.execute(input);
    }
}

8. 业务调用示例

@Autowired
private StrategyService strategyService;

// 1. 字符串加密策略 String -> String
String cipher = strategyService.execute("AES", "hello world");

// 2. 文件解析策略 MultipartFile -> List<String>
MultipartFile file = ...;
List<String> rows = strategyService.execute("CSV_PARSE", file);

✅ 新增策略只需要两步

  1. 新建类 XxxStrategy implements AbstractStrategy<I,O>,实现 execute
  2. 添加 @Component + @Strategy(code="XXX", desc="xxx", inputClass=xxx.class, outputClass=xxx.class)>

无需修改工厂、注册表,无需改任何已有代码


类型安全 & 坑点说明

  1. 泛型擦除问题
    Java运行期无法从实现类直接拿到泛型参数 <I,O>,所以通过注解显式声明 inputClass / outputClass。>

可选增强:注册时增加类型校验方法:调用时校验入参实例是否匹配meta.inputClass,不匹配直接抛异常。

// 在StrategyRegistry增加校验
public <I,O> AbstractStrategy<I,O> getStrategy(String code, I input){
    StrategyMeta meta = getMeta(code);
    if(!meta.getInputClass().isInstance(input)){
        throw new IllegalArgumentException("策略【"+code+"】入参类型不匹配,预期:"+meta.getInputClass().getName());
    }
    return (AbstractStrategy<I,O>) meta.getInstance();
}
  1. 类型转换警告 @SuppressWarnings("unchecked")
    属于可控的类型转换,只要业务传参和注解声明保持一致就安全;可以封装在注册器内部,不扩散到业务层。
  2. 支持不同策略完全不同I/O,非常适合插件化处理器:
    • 支付处理器:AbstractStrategy<PayRequestDTO, PayResultDTO>
    • 报表导出:AbstractStrategy<ReportQuery, byte[]>
    • 消息发送:AbstractStrategy<MsgDTO, SendResult>

可选增强:工具方法,统一类型校验

// StrategyService
public <I,O> O executeWithTypeCheck(String code, I input){
    StrategyMeta meta = strategyRegistry.getMeta(code);
    if (!meta.getInputClass().isInstance(input)) {
        throw new IllegalArgumentException(
                String.format("策略[%s]入参类型错误,预期:%s,实际:%s",
                        code, meta.getInputClass().getName(), input.getClass().getName()));
    }
    AbstractStrategy<I,O> strategy = strategyRegistry.getStrategy(code);
    return strategy.execute(input);
}

可选:换回抽象类版本

如果你想继续使用 abstract class AbstractStrategy<I,O>,只需要把 interface 改成抽象类,其余注册逻辑不变。

扩展:统一异常

public class StrategyExecuteException extends RuntimeException{
    public StrategyExecuteException(String message, Throwable cause) {
        super(message, cause);
    }
}

对比上一版加密专用策略

  • 之前:固定入参出参都是String,加密场景专用
  • 当前泛型版:通用策略框架,任意I/O类型;一套框架支撑多种业务策略
  • 依然保留:注解驱动、自动扫描、启动校验、插件式新增策略
posted on 2026-10-16 14:22  五维思考  阅读(5)  评论(0)    收藏  举报

QQ群:1. 全栈码农【346906288】2. VBA/VSTO【2660245】