SpringBoot+Activiti 创建自定义业务审批框架

流程开发主要分成两部分:

  1. 通用的流程操作(基于Activiti 本身)。
  2. 独立的业务开发。
    而在业务开发中,会伴随着很多与Activiti的代码耦合,会出现大量的同等功能的业务代码,但是由于操作的业务对象不同,又需要写很多冗余的CRUD操作。这样增加了大量低质量的耦合代码。
    为了减少业务代码中的通用性操作代码,可以编写一套自定义的业务代码审批框架。
    项目代码:
    GitHub https://github.com/oldguys/ActivitiDemo
结构类图:
框架设计图 .jpg
设计思路:
  1. 基于设计模式规则: 依赖倒置原则 , 里氏代换原则,将通用的接口方法进行抽象,完成高层接口:WorkEntityMapperProcessInstanceService 设计。
  2. 使用类似于 抽象工厂模式桥梁模式,对系统进行 解耦多态
    CommonWorkEntityService:进行通用的服务调度,像桥梁一样来适配调度多个ProcessInstanceService
    ProcessInstanceService由基于不同业务对象进行实现,通过不同实例,再关联到DAO层的不同业务对象上。
  3. CommonWorkEntityService 继承于 AbstractWorkEntityService,通过调用 init 方法来通用注入业务对象,而实现类则成为统一调度入口。
调度方式:

TaskController 调用 CommonWorkEntityService的getWorkEntityInfo(),最终可以分别获取到Entity1ProcessEntity实体或Entity2ProcessEntity实体

调用图.jpg
关联与对象注入:

由于基于SpringBoot,自然要利用Spring的容器进行管理。
Step1: 由于服务对象实现于接口 ProcessInstanceService,可以去Spring容器中,获取所有实现于该接口的对象。
Step2: 利用泛型来获取:
Entity1ProcessInstanceService 继承于 AbstractProcessInstanceService<T> 实现于 ProcessInstanceService<T>
获取实现类上的泛型,就可以知道业务实体为 Entity1Process,利用Map将对象与业务类别进行关联标识。同理可使用于 DAO层。

@Service
public class Entity1ProcessInstanceService extends AbstractProcessInstanceService<Entity1Process> {

}

Step3: 注入对象,依赖倒置中,注入方法有:设值注入,构造注入,接口注入。这里使用设值注入来实现,关联 ProcessInstanceServiceDAOMapper

Step4: 巧用多态,根据 **里氏代换原则 **,基类可以调用子类,而我们扩展业务对象,一般都采用继承来实现DTO对象,就可以实现不同的子类对象来满足数据的多态。

@Service
public class Entity1ProcessInstanceService extends AbstractProcessInstanceService<Entity1Process> {
    @Override
    public Entity1Process getTarget(Long id) {
        // Entity1ProcessInfo 继承于 Entity1Process 
        return new Entity1ProcessInfo();
    }
}
package com.oldguy.example.modules.workflow.service;


import com.oldguy.example.modules.common.dao.jpas.WorkEntityMapper;
import com.oldguy.example.modules.common.exceptions.FormValidException;
import com.oldguy.example.modules.common.utils.SpringContextUtils;
import com.oldguy.example.modules.workflow.configs.WorkFlowConfiguration;
import com.oldguy.example.modules.workflow.dto.TaskEntityInfo;
import com.oldguy.example.modules.workflow.dto.WorkBtn;
import com.oldguy.example.modules.workflow.dto.WorkEntityInfo;
import com.oldguy.example.modules.workflow.service.entities.ProcessAuditStatusService;
import com.oldguy.example.modules.workflow.service.entities.ProcessTaskConfigService;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.RuntimeService;
import org.apache.commons.lang3.ClassUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author ren
 * @date 2018/12/6
 */
public abstract class AbstractWorkEntityService {

    /**
     * 工作流实体集合
     */
    protected Map<String, ProcessInstanceService> processInstanceServiceMap = Collections.emptyMap();

    /**
     * DAO Mapper
     */
    protected Map<String, WorkEntityMapper> workEntityMapperMap = Collections.emptyMap();

    protected UserTaskService userTaskService;

    protected ProcessService processService;

    protected RuntimeService runtimeService;

    protected ProcessTaskConfigService processTaskConfigService;

    protected ProcessAuditStatusService processAuditStatusService;

    /**
     * 获取抽象类型的泛型类
     *
     * @param object
     * @return
     */
    public static Class getActualTypeArgumentByClassAbstractClass(Object object) {
        Class typeClass = (Class) ((ParameterizedType) object.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return typeClass;
    }

    /**
     * 获取接口的泛型类型
     *
     * @param object
     * @return
     */
    public static Class getActualTypeArgumentByClassInterface(Object object) {

        Type[] types = object.getClass().getGenericInterfaces();
        ParameterizedType parameterizedType = (ParameterizedType) types[0];
        Class typeClass = (Class) parameterizedType.getActualTypeArguments()[0];
        return typeClass;
    }

    /**
     * 获取Mapper接口上的实现泛型
     *
     * @param object
     * @return
     */
    public static Class getActualTypeArgumentByInterface(Object object) {

        List<Class<?>> list = ClassUtils.getAllInterfaces(object.getClass());

        Class clazz = list.get(0);
        Type[] types = clazz.getGenericInterfaces();
        Class typeClass = (Class) ((ParameterizedType) types[0]).getActualTypeArguments()[0];

        return typeClass;
    }

    protected void init() {
        if (null == userTaskService) {
            userTaskService = SpringContextUtils.getBean(UserTaskService.class);
        }
        if (null == processService) {
            processService = SpringContextUtils.getBean(ProcessService.class);
        }
        if (null == runtimeService) {
            runtimeService = SpringContextUtils.getBean(RuntimeService.class);
        }
        if (null == processTaskConfigService) {
            processTaskConfigService = SpringContextUtils.getBean(ProcessTaskConfigService.class);
        }
        if (null == processAuditStatusService) {
            processAuditStatusService = SpringContextUtils.getBean(ProcessAuditStatusService.class);
        }

        // 注入DAO Mapper
        if (workEntityMapperMap.isEmpty()) {
            String[] baseEntityMapperNames = SpringContextUtils.getBeanNamesForType(WorkEntityMapper.class);
            workEntityMapperMap = new HashMap<>(baseEntityMapperNames.length);

            for (String name : baseEntityMapperNames) {
                WorkEntityMapper workEntityMapper = SpringContextUtils.getBean(name, WorkEntityMapper.class);

                // 获取返修类
                Class typeClass = getActualTypeArgumentByInterface(workEntityMapper);
                workEntityMapperMap.put(typeClass.getSimpleName(), workEntityMapper);
            }
        }

        // 注入ProcessInstanceService
        if (processInstanceServiceMap.isEmpty()) {
            String[] workflowNames = SpringContextUtils.getBeanNamesForType(ProcessInstanceService.class);
            processInstanceServiceMap = new HashMap<>(workflowNames.length);

            for (String name : workflowNames) {
                AbstractProcessInstanceService workFlow = SpringContextUtils.getBean(name, AbstractProcessInstanceService.class);

                // 获取返修类
                Class typeClass = getActualTypeArgumentByClassAbstractClass(workFlow);

                // 注入类别名称
                workFlow.setClassName(typeClass.getSimpleName());
                workFlow.setWorkEntityMapper(workEntityMapperMap.get(typeClass.getSimpleName()));
                workFlow.setProcessTaskConfigService(processTaskConfigService);
                workFlow.setProcessAuditStatusService(processAuditStatusService);

                processInstanceServiceMap.put(typeClass.getSimpleName(), workFlow);
            }
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容