跳到主要内容

功能注册引擎设计文档

1. 概述

1.1 设计目标

本设计实现了一个基于注解驱动的业务-模板注册引擎,通过 @UseTemplate 注解在启动时自动绑定业务实现类与对应的处理模板,实现业务逻辑与处理流程的解耦。

1.2 核心特性

  • ✅ 注解驱动的自动注册机制
  • ✅ 泛型类型安全检查
  • ✅ 函数式编程风格
  • ✅ Spring Boot 集成
  • ✅ 灵活的模板化处理流程

2. 核心组件设计

2.1 RegisterEnginV3 - 注册引擎

职责

  • 启动时扫描所有 Business 实现类
  • 解析 @UseTemplate 注解,绑定业务与模板
  • 执行泛型类型一致性检查
  • 提供统一的业务执行入口

关键代码

@Service
@AllArgsConstructor
public class RegisterEnginV3<T, S extends Business<T>> {

@PostConstruct
public void init() {
businessesMap.forEach((txcode, businessService) -> {
// 1. 解析 @UseTemplate 注解
UseTemplate ann = businessService.getClass()
.getAnnotation(UseTemplate.class);

// 2. 获取对应的 Template Bean
Template<T, S> templateToUse =
(Template<T, S>) applicationContext
.getBean(ann.value());

// 3. 泛型类型检查
checkConsistentGenericType(businessService, templateToUse);

// 4. 注册到执行注册表
registry.put(txcode,
param -> templateToUse.handler(txcode, param, businessService));
});
}

public void run(String businessType, T params) {
registry.get(businessType).accept(params);
}
}

泛型参数说明

  • T: 业务处理的参数类型(通常是 BusinessContext<I, O>
  • S: 具体的 Business 实现类型

2.2 Template - 处理模板接口

设计理念: 采用函数式接口设计,定义统一的业务处理流程。

@FunctionalInterface
public interface Template<T, S extends Business<T>> {
void handler(String txcode, T param, S businessService);
}

实现示例

@Component
public class TemplateImpl3<I, O> implements
Template<BusinessContext<I, O>, BusinessType3<I, O>> {

@Override
public void handler(String txcode,
BusinessContext<I, O> param,
BusinessType3<I, O> businessService) {
// 前置处理
// ...

// 执行业务逻辑
businessService.doBusiness2(param);

// 后置处理
// ...
}
}

2.3 Business - 业务接口层次

接口层次设计

Business<T> (标记接口)

BusinessType3<I, O> extends Business<BusinessContext<I, O>>

Business3 implements BusinessType3<InputDto3, OutputDto3>

Business 基础接口

public interface Business<T> {
// 标记接口,用于类型约束
}

业务类型接口

public interface BusinessType3<I, O>
extends Business<BusinessContext<I, O>> {

void doBusiness2(BusinessContext<I, O> businessContext);
}

具体业务实现

@Service
@UseTemplate(TemplateImpl3.class) // 👈 关键注解
public class Business3 implements
BusinessType3<InputDto3, OutputDto3> {

@Override
public void doBusiness2(BusinessContext<InputDto3, OutputDto3> ctx) {
OutputDto3 output = new OutputDto3();
output.setField2("result from Business3");
output.setField3("xxx");
ctx.setOutput(output);
}
}

2.4 @UseTemplate 注解

定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseTemplate {
Class<?> value(); // 指定使用的 Template 实现类
}

使用场景: 在 Business 实现类上标注,指定该业务使用的处理模板。


3. 数据流转设计

3.1 BusinessContext 上下文

@Data
public class BusinessContext<I, O> {
private I input; // 输入参数
private O output; // 输出结果
private OtherDto3 otherDto; // 其他上下文信息
}

3.2 执行流程

客户端调用

RegisterEnginV3.run(businessType, params)

从注册表获取 Consumer<T>

Template.handler(txcode, param, businessService)

BusinessService.doBusiness(param)

返回结果(通过 BusinessContext)

4. 类型安全机制

4.1 泛型类型检查

private static <T, S extends Business<T>> void
checkConsistentGenericType(S businessService,
Template<T, S> templateToUse) {

Class<?> templateGeneric = getGeneric(templateToUse, Template.class);
Class<?> businessGeneric = getGeneric(businessService, Business.class);

if (businessGeneric != null && templateGeneric != null &&
!templateGeneric.isAssignableFrom(businessGeneric)) {
throw new IllegalStateException("❌ Template type mismatch");
}
}

4.2 类型不匹配示例

// ❌ 错误示例
@UseTemplate(TemplateImpl1.class) // 期望 BusinessContext<A, B>
public class Business3 implements
BusinessType3<InputDto3, OutputDto3> { // 实际 BusinessContext<InputDto3, OutputDto3>
// 启动时会抛出 IllegalStateException
}

5. Bean 命名策略

5.1 UniquePackageBeanNameGenerator

问题背景: 当多个包下存在同名 Business 类时,Spring 默认命名策略会导致 Bean 名称冲突。

解决方案

public class UniquePackageBeanNameGenerator
extends AnnotationBeanNameGenerator {

@Override
public String generateBeanName(BeanDefinition definition,
BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
String originalBeanName = super.generateBeanName(definition, registry);

if (beanClassName != null) {
try {
Class<?> clazz = Class.forName(beanClassName);
if (Business.class.isAssignableFrom(clazz)) {
return "NEW" + originalBeanName; // 添加前缀
}
} catch (ClassNotFoundException e) {
return originalBeanName;
}
}
return originalBeanName;
}
}

5.2 配置方式

@SpringBootApplication
@ComponentScan(nameGenerator = UniquePackageBeanNameGenerator.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

6. 使用示例

6.1 定义 DTO

@Data
public class InputDto3 {
private String field1;
}

@Data
public class OutputDto3 {
private String field2;
private String field3;
}

6.2 实现业务逻辑

@Service
@UseTemplate(TemplateImpl3.class)
public class Business3 implements
BusinessType3<InputDto3, OutputDto3> {

@Override
public void doBusiness2(BusinessContext<InputDto3, OutputDto3> ctx) {
InputDto3 input = ctx.getInput();

OutputDto3 output = new OutputDto3();
output.setField2("处理结果: " + input.getField1());
output.setField3("额外信息");

ctx.setOutput(output);
}
}

6.3 调用业务

@RestController
@AllArgsConstructor
public class BusinessController {

private final RegisterEnginV3<BusinessContext<?, ?>, ?> registerEngine;

@PostMapping("/execute/{businessType}")
public BusinessContext<?, ?> execute(
@PathVariable String businessType,
@RequestBody InputDto3 input) {

BusinessContext<InputDto3, OutputDto3> context =
new BusinessContext<>();
context.setInput(input);

registerEngine.run(businessType, context);

return context;
}
}

7. 优势与扩展性

7.1 设计优势

特性说明
低耦合业务逻辑与处理流程完全分离
高内聚相同类型业务共享同一模板
类型安全编译期 + 运行期双重类型检查
易扩展新增业务只需实现接口并添加注解
可维护统一的注册和执行机制

8. 异常处理

  1. 定义系统内部异常和自定义通信异常
  2. Controller 或者 Service 统一处理自定义异常和未知异常
  3. 如果有不需要响应的场景,自定义一个不响应的异常
  4. 如果有多个远程调用,按多种远程调用的自定义异常来处理

9. 总结

本设计通过注解驱动的方式实现了业务逻辑的灵活注册和执行,主要特点:

声明式配置:通过 @UseTemplate 注解绑定关系

类型安全:泛型 + 反射实现编译期和运行期双重检查

高度解耦:业务逻辑与执行流程完全分离

易于扩展:新增业务无需修改注册引擎代码

适用场景:

  • 多业务类型的统一处理框架
  • 需要动态扩展业务的系统
  • 业务流程标准化的场景

协议
本作品代码部分采用Apache 2.0协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:
  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留Apache 2.0协议文件。
本作品文档部分采用知识共享署名 4.0 国际许可协议 进行许可。遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:
  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0国际许可协议进行许可。