activiti7流程设计器_设计模式综合运⽤
1. 门⾯+模版⽅法+责任链+策略
1.1 项⽬背景
在公司的⼀个实际项⽬中,需要做⼀个第三⽅公司(以下简称XHF)的系统集成⼯作,把该公司的⼀些订单数据集成到⾃⼰公司平台下,各个
订单具有⼀些共性,但是也有其特有的特征。 经过设计,⽬前我把订单分为POLICY和XHF类型(暂且这么说吧,反正就是⼀种订单类型,
⼤家参照着看就OK)。
在订单数据集成到公司平台前,需要对订单数据进⾏⼀些必要的业务逻辑校验操作,并且每个订单都有⾃⼰的校验逻辑(包含公共的校验逻
辑)。 本节介绍的便是整个订单集成系统中的校验逻辑在综合利⽤设计模式的基础上进⾏架构设计。
1.2 校验逻辑
本校验逻辑主要分为四个部分:
1. 校验⽂件名称(RequestValidator.validateFileInfo)
2. 校验⽂件内容中的概要部分(RequestValidator.validateSummary)
3. 校验⽂件内容中的列名称(RequestValidator.validateHeaders)
4. 校验⽂件内容中的明细(RequestValidator.validateDetails)
其实上⾯的RequestValidator的实现逻辑最后都是委托给RequestValidationFacade这个门⾯类进⾏相应的校验操作。
1.3 实现细节
1.3.1 domain介绍
主要分为RequestFile和RequestDetail两个domain,RequestFile接收泛型的类型(即RequestFile), 使得其⼦类能够⾃动识别相应的RequestDetail的⼦类。RequestFile为抽象类,定义了以下抽象⽅法,由⼦类实现:
/
/由⼦类实现具体的获取⽂件明细内容public abstract List getRequestDetails();//由⼦类实现具体的获取workflow的值public abstract WorkflowEnum getProcessWorkF
RequestDetail及其⼦类就是workflow对应⽂件的明细内容。
1.3.2 WorkflowEnum枚举策略
本例中如下规定:
1. workflow为WorkflowEnum.POLICY对应⽂件名为:xhf_policy_yyyyMMdd_
2. workflow为WorkflowEnum.XHF对应⽂件名为:xhf_integration_yyyyMMdd_
以上校验逻辑在AbstractRequestValidation类相应的⼦类中实现(validateFileName⽅法),其实这个枚举贯穿整个校验组件,它就是⼀个
针对每个业务流程定义的⼀个枚举策略。
1.3.3 涉及到的设计模式实现思路
1.3.3.1 门⾯模式
在客户端调⽤程序中,采⽤门⾯模式进⾏统⼀的⼊⼝(门⾯模式讲究的是脱离具体的业务逻辑代码)。门⾯模式封装的结果就是避免⾼层模块
深⼊⼦系统内部,同时提供系统的⾼内聚、低耦合的特性。
此案例中,门⾯类为RequestValidationFacade,然后各个门⾯⽅法的参数均为抽象类RequestFile,通过RequestFile-
>getProcessWorkFlow()决定调⽤AbstractRequestValidation中的哪个⼦类。 AbstractRequestValidation类构造⽅法中定义了如下
逻辑:
requestValidationHandlerMap.put(this.accessWorkflow(),this.accessBeanName());
把⼦类中Spring⾃动注⼊的实体bean缓存到requestValidationHandlerMap中,key即为WorkflowEnum枚举值,value为spring bean name, 然后在门⾯类中可以通过对应的枚举值取得BeanName,进⽽得到AbstractRequestValidation相应的⼦类对象,进⾏相应的校验
操作。
注:这边动态调⽤到AbstractRequestValidation相应的⼦类对象,其实也是隐藏着【策略模式】的影⼦。
1.3.3.2 模版⽅法模式
在具体的校验逻辑中,⽤到核⼼设计模式便是模版⽅法模式,AbstractRequestValidation抽象类中定义了以下抽象⽅法:
/** * validate the file details * @param errMsg * @param requestFile * @return */ protected abstract StringBuilder validateFileDetails(String
以上抽象⽅法就类似我们常说的钩⼦函数,由⼦类实现即可。
validation框架1.3.3.3 责任链模式
在AbstractRequestValidation抽象类中有个抽象⽅法validateFileDetails,校验的是⽂件的明细内容中的相应业务规则,此为核⼼校验,
较为复杂,⽽且针对每个业务流程,其校验逻辑相差较⼤,在此处,利⽤了责任链模式进⾏处理。
Validator为校验器的⽗接⼝,包含两个泛型参数(即:),其实现类可以⽅便的转换需要校验的⽂件明细。
String doValidate(R detail, F file, ValidatorChain chain) throws BusinessValidationException;
该⽅法含有⼀个ValidatorChain参数,就⾃然⽽然的为该校验器形成⼀个链条提供便利条件。
ValidatorChain为校验器链,含有两个接⼝⽅法:
String doValidate(T requestDetail, F requestFile) throws BusinessValidationException; ValidatorChain addValidator(Validator validator, WorkflowEnum wo
该处有⼀个addValidator⽅法,为ValidatorChain对象添加校验器的⽅法,返回本⾝。对应于每个业务流程需要哪些校验器就在此实现即
可(即AbstractRequestValidation的⼦类⽅法validateFileDetails)。
1.3.3.4 策略模式
如果单单从上⾯的校验器实现上来看,如果需要增加⼀个校验器,就需要在AbstractRequestValidation的⼦类⽅法validateFileDetails中
添加,然后进⾏相应的校验操作。这样就会⾮常的⿇烦,没有做到真正的解耦。 此时,策略模式就发挥到了可以动态选择某种校验策略的
作⽤(Validator的实现类就是⼀个具体的校验策略)。
AbstractValidatorHandler抽象类持有FileDetailValidatorChain类的对象,并且实现累Spring的⼀个接⼝ApplicationListener(是为了Spring容器启动完成的时候⾃动把相应的校验器加⼊到校验器链中)。 核⼼就是WorkflowEnum这个策略枚举的作⽤,在⼦类可以动态的
取得相应的校验器对象。
根据⼦类提供需要的校验器所在的包名列表和不需要的校验器列表,动态配置出需要的校验器链表。核⼼实现逻辑如下:
private void addValidators() { List> validators = getValidators(); validators.forEach((validator) -> { String simpleName = SimpleName();具体实现可以参考代码即可。
该类含有以下⼏个抽象⽅法:
protected abstract WorkflowEnum getWorkflowId();/** * the package need to be added the validators * @return */protected abstract Set getBasePackages(
2. 门⾯+模版⽅法+责任链+策略+⼯⼚⽅法
上⼀节在实现策略模式的实现上,发现了⼀个弊端:那就是如果在后续业务发展中,需要再次增加⼀个业务策略的时候,则需要再次继承AbstractValidatorHandler类(详情请参见上篇⽂章),这样就会造成⼀定的类膨胀。今天我利⽤注解的⽅式改造成动态策略模式,这样就只需要关注⾃⼰的业务类即可,⽆需再实现⼀个类似的Handler类。
2.1 项⽬背景
2.1.1 项⽬简介
在公司的⼀个业务系统中,有这样的⼀个需求,就是根据不同的业务流程,可以根据不同的组合主键策略进⾏动态的数据业务查询操作。在本⽂中,我假设有这样两种业务,客户信息查询和订单信息查询,对应以下枚举类:
/** * 业务流程枚举 * @author landyl * @create 11:18 AM 05/07/2018 */public enum WorkflowEnum { ORDER(2), CUSTOMER(3), ; }
每种业务类型都有⾃⼰的组合主键查询规则,并且有⾃⼰的查询优先级,⽐如客户信息查询有以下策略:
1. customerId
2. requestId
3. birthDate+firstName
以上仅是假设性操作,实际业务规则⽐这复杂的多
2.1.2 流程梳理
主要业务流程,可以参照以下简单的业务流程图。
2.1.2.1 查询抽象模型
2.1.2.2 组合主键查询策略
2.1.2.3 组合主键查询责任链
2.2 Java注解简介
注解的语法⽐较简单,除了@符号的使⽤之外,它基本与Java固有语法⼀致。
2.2.1 元注解
JDK1.5提供了4种标准元注解,专门负责新注解的创建。
注解说明@Target表⽰该注解可以⽤于什么地⽅,可能的ElementType参数有:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:⽅法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类、接⼝(包括注解类型)或enum声明@Retention表⽰需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃
CLASS:注解在class⽂件中可⽤,但会被VM丢弃
RUNTIME:JVM将在运⾏期间保留注解,因此可以通过反射机制读取注解的信息@Document将注解包含在Javadoc中@Inherited允许⼦
类继承⽗类中的注解
2.2.2 ⾃定义注解
定义⼀个注解的⽅式相当简单,如下代码所⽰:
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documented//使⽤@interface关键字定义注解public @
注解的可⽤的类型包括以下⼏种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,
即要么有默认值,要么在使⽤注解的时候提供元素的值。⽽且元素不能使⽤null作为默认值。注解在只有⼀个元素且该元素的名称是value
的情况下,在使⽤注解的时候可以省略“value=”,直接写需要的值即可。
2.2.3 使⽤注解
如上所⽰的注解使⽤如下:
/** * @author landyl * @create 2018-01-12:39 PM *///在类上使⽤定义的Description注解@Description(value="class annotation",count=2)public class Person { priv
使⽤注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。从原理上讲,注解处理器就是通过反射机制获取被检查⽅法上的注
解信息,然后根据注解元素的值进⾏特定的处理。
/** * @author landyl * @create 2018-01-12:35 PM * 注解解析类 */public class ParseAnnotation { public static void main(String[] args){ //使⽤类加载器加载类
2.3 策略模式升级版
2.3.1 策略模式实现⽅式
1. 使⽤⼯⼚进⾏简单的封装
2. 使⽤注解动态配置策略
3. 使⽤模版⽅法模式配置策略(参见1. 门⾯+模版⽅法+责任链+策略)
4. 使⽤⼯⼚+注解⽅式动态配置策略(利⽤Spring加载)
其中第1、2点请参见org.landy.strategy 包下的demo事例即可,⽽第4点的⽅式其实就是结合第1、2、3点的优点进⾏整合的⽅式。
2.3.2 注解⽅式优点
使⽤注解⽅式可以极⼤的减少使⽤模版⽅法模式带来的扩展时需要继承模版类的弊端,⼯⼚+注解的⽅式可以⽆需关⼼其他业务类的实现,
⽽且减少了类膨胀的风险。
2.3.3 组合主键查询策略
本⽂以组合主键查询策略这⼀策略进⾏说明,策略注解如下:
/** * 组合主键查询策略(根据不同业务流程区分组合主键查询策略,并且每个业务流程都有⾃⼰的优先级策略) * @author landyl * @create 2:22 PM 09/29/2018 */@Targ
2.3.4 策略⼯⼚
既然定义了组合主键查询策略注解,那必然需要⼀个注解处理器进⾏解析注解的操作,本⽂以⼯⼚的⽅式进⾏。主要逻辑如下:
1. 扫描指定包下的Java类,出相应接⼝(即KeyIdentification)下的所有Class对象。private List> getIdentifications() { Set
packageNames = BasePackages(); List> identifications = new ArrayList<>(); if(packageNames != null) {
packageNames.forEach((packageName) -> identifications.addAll(getIdentifications(packageName))); } return
identifications; }
2. 解析注解KeyIdentificationStrategy,定义⼀个排序对象(KeyIdentificationComparator),指定优先级。private class
KeyIdentificationComparator implements Comparator { @Override public int compare(Object objClass1, Object
objClass2) { if(objClass1 != null && objClass2 != null) { Optional strategyOptional1 =
getPrimaryKeyIdentificationStrategy((Class)objClass1); Optional strategyOptional2 =
getPrimaryKeyIdentificationStrategy((Class)objClass2); KeyIdentificationStrategy ip1 = ();
KeyIdentificationStrategy ip2 = (); Integer priority1 = ip1.priority(); Integer priority2 =
ip2.priority(); WorkflowEnum workflow1 = ip1.workflowId(); WorkflowEnum workflow2 = ip2.workflowId(); //先按业务类
型排序 int result = Value() - Value(); //再按优先级排序 if(result == 0) return
priority1pareTo(priority2); return result; } return 0; } }
3. 根据注解,把相应业务类型的组合主键查询策略对象放⼊容器中(即DefaultKeyIdentificationChain)。KeyIdentificationStrategy
strategy = (); String beanName = strategy.beanName(); //业务流程类型 WorkflowEnum workflowId = strategy.workflowId(); KeyIdentificationStrategy priority = getPrimaryKeyIdentificationStrategy(v).get();
LOGGER.info("To add identification:{},spring bean name is:{},the identify priority is:{},workflowId:
{}",simpleName,beanName,priority.priority(),workflowId.name()); KeyIdentification instance =
defaultKeyIdentificationChain.addIdentification(instance,workflowId);
4. 后续,在各⾃对应的业务查询组件对象中即可使⽤该⼯⼚对象调⽤如下⽅法,即可进⾏相应的查询操作。
public IdentificationResultType identify(IdentifyCriterion identifyCriterion,WorkflowEnum workflowId) { defaultKeyIdentificationChain.doClearIdentificatio
3. 动态代理+Spring AOP
AOP设计模式通常运⽤在⽇志,校验等业务场景,本⽂将简单介绍基于Spring的AOP代理模式的运⽤。
3.1 Spring AOP
3.1.1 Spring AOP原理
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论