一种对某个操作前置后置一系列处理的优化经验

背景与优化思路

在项目的某几个接口,例如流程提交接口,在“调用工作流引擎,改变工作流状态”这个操作的前后,都会有一系列的处理,而且改动频繁,基本上新增一块业务功能就需要在流程提交接口中做些校验或者更新的操作。原本的流程提交代码类似于这样:

1
2
3
4
5
6
7
8
9
// 提交前1:当XX时,校验XX内容
// 提交前2:当XX时,校验XX内容

// 流程提交主体逻辑:组装工作流引擎参数,调用工作流引擎,改变工作流状态

// 提交后1:当XX时,更新业务状态
// 提交后2:当XX时,更新XX内容
// 提交后3:当XX时,保存历史数据
// 提交后4、5、6等

所以,只要新增一块业务需求,都要改动这段代码,非常地麻烦。我要把它优化成,这段代码只保留主体逻辑,即“调用工作流引擎”那部分,在这部分前、后的涉及业务处理的代码,都封装成一个接口。加需求时,我只是添加一个接口实现类,不需要改动这块主体逻辑。

于是,我想到了 Spring 提供的接口BeanPostProcessor,它的特点就是,用户只要添加一个它的实现类,实现它的两个方法:postProcessBeforeInitializationpostProcessAfterInitialization,在 Bean 被初始化的前后,就会调用到用户添加的实现类,而用户并不需要改动 Bean 初始化的主体逻辑,就像切面一样。

但是,BeanPostProcessor 有个不灵活的地方,就是它的切入点只能是“Bean 初始化”前后。想要增加切入点,就得再写一个接口继承它,就像InstantiationAwareBeanPostProcessoror做的那样,它扩展的切入点是“Bean 实例化”。几个切入点就要几个接口。

于是,我又想到了 Spring Cloud 中的 Zuul 网关,它将自己的过滤器分成几种类型:pre、route、post、error,这个类型决定了这个过滤器在哪个时间点被执行,并且可以指定过滤器的执行顺序。用户想要在多个时间点添加自己的过滤器,都只实现一个抽象类ZuulFilter,指定过滤器类型filterType和执行顺序filterOrder,Zuul 就会按照它原本的主体逻辑,在合适的时候调用到用户添加的过滤器,用户也不需要改动 Zuul 的逻辑。

优化代码

结合 BeanPostProcessor 和 ZuulFilter 的特点和它们的源码,我定义了如下抽象类。类中方法的入参 WorkflowInBO 、WorkflowOutBO 是我封装的在调用工作流引擎前后都能获取到的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public abstract class WorkflowPostProcessor implements Comparable<WorkflowPostProcessor> {
public static int MAX_ORDER = 999;

/**
* 定义processor的类型,可选值在 ProcesssorTypeEnum 枚举类定义
*/
public abstract ProcesssorTypeEnum processsorType();

/**
* 返回值决定processor的执行顺序,值小的先执行
*/
public int processsorOrder() {
return MAX_ORDER;
}

/**
* 这个方法返回TRUE时,才会执行这个processor的前置、后置方法;否则都不执行。可由子类覆盖
*/
public boolean shouldProcess(WorkflowInBO inBO) {
return true;
}

/**
* 在调用工作流引擎前,执行此方法
*/
public void invokePostProcessorBeforeAction(WorkflowInBO inBO) {
if (shouldProcess(inBO)) {
postProcessorBeforeAction(inBO);
}
}

/**
* 在调用工作流引擎后,执行此方法
*/
public void invokePostProcessorAfterAction(WorkflowInBO inBO, WorkflowOutBO outBO) {
if (shouldProcess(inBO)) {
postProcessorAfterAction(inBO, outBO);
}
}

/**
* 由子类实现
*/
public abstract void postProcessorBeforeAction(WorkflowInBO inBO);

/**
* 由子类实现
*/
public abstract void postProcessorAfterAction(WorkflowInBO inBO, WorkflowOutBO outBO);

/**
* 排序方法
*/
@Override
public int compareTo(WorkflowPostProcessor o) {
return Integer.compare(this.processsorOrder(), o.processsorOrder());
}
}

我还定义一个“统一入口”,用来访问WorkflowPostProcessor的所有实现类的前置后置方法。这个主要是参考 Zuul 的FilterProcessor的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Component
public class ProcessorsManager {
@Autowired
List<WorkflowPostProcessor> allProcessors;

private final ConcurrentHashMap<String, List<WorkflowPostProcessor>> hashProcessorsByType = new ConcurrentHashMap<>();

/**
* 从 allProcessors 中过滤出指定 processsorType 的处理器,并排序,返回List
*/
private List<WorkflowPostProcessor> getProcessorsByType(ProcesssorTypeEnum processsorType) {
List<WorkflowPostProcessor> list = hashProcessorsByType.get(processsorType.name());
if (list != null) return list;

list = new ArrayList<>();
for (WorkflowPostProcessor processor : allProcessors) {
if (processor.processsorType() == processsorType) {
list.add(processor);
}
}
Collections.sort(list);
hashProcessorsByType.putIfAbsent(processsorType.name(), list);
return list;
}

/**
* 遍历执行指定 processsorType 的processor的前置方法
*/
public void runPostProcessorBeforeActionByType(ProcesssorTypeEnum processsorType, WorkflowInBO inBO) {
List<WorkflowPostProcessor> processors = getProcessorsByType(processsorType);
if (!processors.isEmpty()) {
for (WorkflowPostProcessor processor : processors) {
processor.invokePostProcessorBeforeAction(inBO);
}
}
}

/**
* 遍历执行指定 processsorType 的processor的后置方法
*/
public void runPostProcessorAfterActionByType(ProcesssorTypeEnum processsorType, WorkflowInBO inBO, WorkflowOutBO outBO) {
List<WorkflowPostProcessor> processors = getProcessorsByType(processsorType);
if (!processors.isEmpty()) {
for (WorkflowPostProcessor processor : processors) {
processor.invokePostProcessorAfterAction(inBO, outBO);
}
}
}
}

于是,流程提交接口的代码就变成了如下这样:

1
2
3
4
5
6
7
// 提交前
processorsManager.runPostProcessorBeforeActionByType(ProcesssorTypeEnum.SUBMIT, workflowInBO);

// 流程提交主体逻辑:组装工作流引擎参数,调用工作流引擎,改变工作流状态

// 提交后
processorsManager.runPostProcessorAfterActionByType(ProcesssorTypeEnum.SUBMIT, workflowInBO, workflowOutBO);

从此以后,负责设计开发新需求的同事就不用来找我说流程接口要加这个加那个了(我还要和他对清楚哪个情况加,怎么加,出BUG了还首先问我是不是我调用参数写错了)直接把这个接口定义扔给他,让他自己加,出BUG都是他的事!莫挨老子!