关于Activiti6的封装设计方案

作者

本文的作者是我部门的同事:凌海天,一个热衷于技术的LOLer,在Activiti方面有不少经验,这次的封装也是基于我们部门的整体Web开发框架的发展目的而做的。

出现问题

在使用act的过程中,我们发现使用act时有些地方可能需要一点包装,因为不同业务发起流程写入的代码却不尽相同,为了防止代码的坏味道,我们打算对act进行一些对应我们公司业务的一些延伸,以方便我们能够更加的方便使用

解决思路

通过将activit简单的封装一层,打成jar包后放入我们的maven私服中,所以我遇到了第一个问题:如何封装activiti。。。

配置activiti

  1. 问题:在一个纯净的环境下配置activiti并且将重要资源配置外置

    在网上找绝大多数activiti配置都是通过xml的方式,但是如果使用xml的话就不能将数据源放到外面动态的配置了(也可能是我太渣,没有深入了解的锅),所以只能自己慢慢来了;思路:看着xml的配置将其中的类取出并做相应配置

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
//引入maven

<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>6.0.0</version>
</dependency>


//配置act

//act引擎
private ProcessEngine processEngine;
//act引擎配置
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
//act引擎生成工厂
private ProcessEngineFactoryBean processEngineFactoryBean;


/**
* @author lht
* @doc 初始化act事务
* @date 2018/6/8
* @param dataSource
* @return
*/
private DataSourceTransactionManager initTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}


/**
* @author lht
* @doc 获得act引擎实例
* @date 2018/6/8
* @param dataSource
* @param dataSourceTransactionManager
* @return
*/
public final ProcessEngine setProcessEngine(DataSource dataSource, DataSourceTransactionManager dataSourceTransactionManager) throws Exception {
this.springProcessEngineConfiguration.setTransactionManager(dataSourceTransactionManager);
this.springProcessEngineConfiguration.setDataSource(dataSource);
this.processEngineFactoryBean.setProcessEngineConfiguration(this.springProcessEngineConfiguration);
this.processEngine = this.processEngineFactoryBean.getObject();
return this.processEngine;
}
  1. 避免一个数据源生多个流程引擎,将流程引擎藏在了ActSpringFactory类中,用以生成act常用的方法

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class ActSpringFactory implements ProcessEngine{


//act引擎
private ProcessEngine processEngine;
//act配置
private CasActConfig casActConfig;
//内部myBatis的sqlSession
private SqlSessionTemplate sqlSessionTemplate;


public ProcessEngine getProcessEngine() {
return processEngine;
}

public void setProcessEngine(ProcessEngine processEngine) {
this.processEngine = processEngine;
}

public SqlSessionTemplate getSqlSessionTemplate() {
return sqlSessionTemplate;
}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}


/**
* @author lht
* @doc 创建ActSpringFactory
* @date 2018/6/8
* @param dataSource
* @return
*/

public ActSpringFactory(DataSource dataSource) {
this.casActConfig = new CasActConfig();
try{
processEngine = casActConfig.setProcessEngine(dataSource);
}catch (Exception e){
e.printStackTrace();
}

}

/**
* @author lht
* @doc 创建ActSpringFactory
* @date 2018/6/8
* @param dataSource
* @param dataSourceTransactionManager
* @return
*/

public ActSpringFactory(DataSource dataSource, DataSourceTransactionManager dataSourceTransactionManager) {
this.casActConfig = new CasActConfig();
try{
processEngine = casActConfig.setProcessEngine(dataSource,dataSourceTransactionManager);
}catch (Exception e){
e.printStackTrace();
}
}




public RuntimeService getRuntimeService(){
return processEngine.getRuntimeService();
}

public FormService getFormService(){
return processEngine.getFormService();
}
public HistoryService getHistoryService(){
return processEngine.getHistoryService();
}
public IdentityService getIdentityService(){
return processEngine.getIdentityService();
}
public ManagementService getManagementService(){
return processEngine.getManagementService();
}

public DynamicBpmnService getDynamicBpmnService() {
return processEngine.getDynamicBpmnService();
}

public ProcessEngineConfiguration getProcessEngineConfiguration() {
return processEngine.getProcessEngineConfiguration();
}

public FormRepositoryService getFormEngineRepositoryService() {
return processEngine.getFormEngineRepositoryService();
}

public org.activiti.form.api.FormService getFormEngineFormService() {
return processEngine.getFormEngineFormService();
}

public String getName() {
return processEngine.getName();
}

public void close() {
processEngine.close();
}
public RepositoryService getRepositoryService(){
return processEngine.getRepositoryService();
}
public TaskService getTaskService(){
return processEngine.getTaskService();
}
//...
}

到这为止activiti就算是配置好了,如果要用的话可以打个jar包直接就使用了,但是这并没有对activiti的功能进行扩展,下面将开始简单的说一下当初遇到这个问题时的想法,在这里也算是一个记录,不至于几天之后忘得一干二净。

抛出问题

假如,现在的业务是有多个流程,每个流程都的具体业务不一样,但是他们发起流程的方式是一样的,都是将业务基础信息放入一个表中,启动流程,,将其中能够简单展示的部分提出和activiti的流程id一起放入到一个中心表,在审批时只需要查询中心表列出列表,在审批时如果需要查看详细信息,可以根据基本信息id去对应的信息表查询。到这就是一个基本的流程闭环了,但是问题也出现了
logo

从图中可以看出,虽然流程业务保存的数据都不相同,但是他们的方式是一样,很明显的可以看出能够进行合并,而设计模式中策略模式就非常的合适这一业务,我们来回顾一下策略模式(引自百度百科):

定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化

流程策略的设计思路

  1. 首先根据策略模式的定义,我们要定一个接口,是所有将要发起流程的dao都要去实现的顶层接口

1
2
3
4
5
6
7
8
9
10
11
/**
* @author lht
* @doc 顶层接口,用于不同业务中的流程启动和数据获取
* @date 2018/6/8
*/

public interface ActBaseDao<T extends ActBaseBean> {
//新增数据
int insert(T t);
//获得数据
List<T> getMsg(T t);
}
  1. 定义接口后发现数据来源也必须确定,所以规定了bean的顶层父类。。。(只是先放了一些我们业务会用到的一些常用参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author lht
* @doc 基础bean,为bean的顶层
* @date 2018/6/8
*/

public class ActBaseBean {
//业务id
private String bssId;
//流程id
private String actId;
//业务类型
private String bssType;
//流程发起人
private String createUser;
//申请时间
private Date createDate;

//get... set...
}
  1. 在将接口和bean都配置好后我们还要讲他们给拼装起来,不然是没办法找到对应bean的,为避免数据的不纯净,所以首先规定类dao和bean对应防止的类DaoAndBean,并规定了范围;然后是存放的容器ActDaoAndBeanMap,其实就是一个map,只是在put进map时简单的检测了一下数据的正确性,并且因为不能确定一个业务的数据表的数量,也就是bean和dao的对应数量,所以做成了可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author lht
* @doc dao和bean成对存放的容器
* @date 2018/6/8
*/

public class DaoAndBean<T extends ActBaseBean,K extends ActBaseDao> {

private Class<T> actBean;
private Class<K> actDao;

public DaoAndBean(Class<T> actBean, Class<K> actDao) {
this.actBean = actBean;
this.actDao = actDao;
}

public Class<T> getActBean() {
return actBean;
}

public Class<K> getActDao() {
return actDao;
}
}
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
/**
* @author lht
* @doc 用于存放业务和dao和bean的关系
* @date 2018/6/8
*/

public class ActDaoAndBeanMap {
private final Map<String,DaoAndBean[]> map;

public ActDaoAndBeanMap() {
this.map = new HashMap<>();
}

public DaoAndBean[] put(String bssType, DaoAndBean... daoAndBeans){
for(DaoAndBean daoAndBean:daoAndBeans){
if(!ActBaseDao.class.isAssignableFrom(daoAndBean.getActDao())||!ActBaseBean.class.isAssignableFrom(daoAndBean.getActBean())){
throw new RuntimeException("Implanting parameter type error");
}
}
return (DaoAndBean[])map.put(bssType,daoAndBeans);
}


public DaoAndBean[] get(String bssType){
return (DaoAndBean[]) map.get(bssType);
}


}
  1. 最后的策略调用,两个主要方法:insert和getMsg,这两个方式就是将顶层接口的方法根据存入map的配对取出后,循环的执行,其他的方法都是启动流程,加入了activiti的start方式

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* @author lht
* @doc 驱动actBaseDao实现
* @date 2018/6/8
*/

public class ActBaseUtils {

ActDaoAndBeanMap actDaoAndBeanMap ;

RuntimeService runtimeService;

ApplicationContext applicationContext;

/**
* @author lht
* @doc 初始化
* @date 2018/6/8
* @param actDaoAndBeanMap
* @param runtimeService
* @param applicationContext
* @return
*/

public ActBaseUtils(ActDaoAndBeanMap actDaoAndBeanMap, RuntimeService runtimeService, ApplicationContext applicationContext) {
this.actDaoAndBeanMap = actDaoAndBeanMap;
this.runtimeService = runtimeService;
this.applicationContext = applicationContext;
}

/**
* @author lht
* @doc 新增业务数据
* @date 2018/6/8
* @param bssType 业务类型
* @param beans 数据
* @return
*/

public void insert(String bssType, ActBaseBean... beans){
//根据业务获得对应dao和bean,执行新增方法
DaoAndBean[] daoAndBeans = actDaoAndBeanMap.get(bssType);
//遍历dao
for(DaoAndBean daoAndBean : daoAndBeans){
ActBaseDao actBaseDao = (ActBaseDao)applicationContext.getBean(daoAndBean.getActDao());
//遍历bean,执行方法
for(ActBaseBean o : beans){
if(o.getClass()==daoAndBean.getActBean()){
actBaseDao.insert(o);
}
}
}
}

/**
* @author lht
* @doc 获得业务数据
* @date 2018/6/8
* @param bssType
* @param resultBean 数据和返回数据类型
* @return
*/

public <T extends ActBaseBean> List<T> getMsg(String bssType,T resultBean){
//根据业务获得对应dao和bean,执行新增方法
DaoAndBean[] daoAndBeans = actDaoAndBeanMap.get(bssType);
//遍历dao获得对应方法
for(DaoAndBean daoAndBean : daoAndBeans){
ActBaseDao actBaseDao = (ActBaseDao)applicationContext.getBean(daoAndBean.getActDao());
//遍历bean,执行方法
if(resultBean.getClass()==daoAndBean.getActBean()){
return actBaseDao.getMsg(resultBean);
}
}
return null;
}

/**
* @author lht
* @doc 启动流程并新增业务数据
* @date 2018/6/8
* @param bssType 业务类型
* @param actKey 流程id
* @param beans 数据
* @return
*/

public void startProcess(String bssType, String actKey,ActBaseBean... beans){
insert(bssType,beans);
//activiti开启流程
runtimeService.startProcessInstanceByKey(actKey);
}
/**
* @author lht
* @doc 启动流程并新增业务数据
* @date 2018/6/8
* @param bssType 业务类型
* @param actKey 流程id
* @param bssId 业务id
* @param beans 数据
* @return
*/

public void startProcess(String bssType, String actKey,String bssId,ActBaseBean... beans){
insert(bssType,beans);
//activiti开启流程
runtimeService.startProcessInstanceByKey(actKey,bssId);
}

/**
* @author lht
* @doc 启动流程并新增业务数据
* @date 2018/6/8
* @param bssType 业务类型
* @param actKey 流程id
* @param actMap 流程参数
* @param beans 数据
* @return
*/

public void startProcess(String bssType, String actKey, Map<String,Object> actMap,ActBaseBean... beans){
insert(bssType,beans);
//activiti开启流程
runtimeService.startProcessInstanceByKey(actKey,actMap);
}

/**
* @author lht
* @doc 启动流程并新增业务数据
* @date 2018/6/8
* @param bssType 业务类型
* @param actKey 流程id
* @param bssId 业务id
* @param actMap 流程参数
* @param beans 数据
* @return
*/

public void startProcess(String bssType, String actKey,String bssId, Map<String,Object> actMap,ActBaseBean... beans){
insert(bssType,beans);
//activiti开启流程
runtimeService.startProcessInstanceByKey(actKey,bssId,actMap);
}



}

好了,到这为止就已经全部说完我的一些想法了,目前只是刚刚写完,还没有经过真实、具体的业务测试,所以如果存在任何问题,本人概不负责…

再加上一点配置demo

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
@Component
public class ActConfig {
@Autowired
DataSource dataSource;

@Autowired
ActSpringFactory actSpringFactory;


/**
* @author lht
* @doc 核心 配置ProcessEngine
* @date 2018/6/6
* @param
* @return
*/

@Bean
public ActSpringFactory getActSpringFactory(){
return new ActSpringFactory(dataSource);
}


@Bean
public ActBaseUtils setActDaoAndBeanFactory(){
return new ActBaseUtils(
getActDaoAndBeanMap(),
actSpringFactory.getRuntimeService(),
SpringUtils.getApplicationContext());
}

//配置具体业务和dao、bean关联
private ActDaoAndBeanMap getActDaoAndBeanMap(){
ActDaoAndBeanMap actDaoAndBeanMap = new ActDaoAndBeanMap();
actDaoAndBeanMap.put("user"
,new DaoAndBean<>(SysUser.class,SysUserMapper.class)
,new DaoAndBean<>(SysRoleUser.class,SysRoleUserMapper.class));
return actDaoAndBeanMap;
}

}

如果是使用springboot作为项目基础框架的话,你们需要这个(获取spring的ApplicationContext)

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
@Component
public class SpringUtils implements ApplicationContextAware {



private static ApplicationContext appCtx;

/**
* 此方法可以把ApplicationContext对象inject到当前类中作为一个静态成员变量。
*
* @param applicationContext ApplicationContext 对象.
* @throws BeansException
* @author wangdf
*/

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appCtx = applicationContext;
}

/**
* 获取ApplicationContext
*
* @return
* @author wangdf
*/

public static ApplicationContext getApplicationContext() {
return appCtx;
}

/**
* 这是一个便利的方法,帮助我们快速得到一个BEAN
*
* @param beanName bean的名字
* @return 返回一个bean对象
* @author wangdf
*/

public static Object getBean(String beanName) {
return appCtx.getBean(beanName);
}
public static Object getBean(Class beanClass) {
return appCtx.getBean(beanClass);
}

}

再来一个架构图

logo

最后附上源码和demo

源码地址:https://github.com/lhtGit/MyHandyAct

对应测试:https://github.com/lhtGit/MyHandyActDemo

热评文章