谈起节点缓存,可能读者有个疑问,缓存节点的意义何在,哪些节点支持缓存呢?需要在哪里配置节点缓存值呢?节点缓存的数据格式又是什么呢?首先我们有一种常见的应用场景,比如我们定义流程文档的时候,通常任务节点的使用会非常的频繁,如果我们将流程文档部署之后,流程实例运行了一段时间突然发现任务节点的名称、分类或者处理人需要修改,这个时候我们该怎么办呢?聪明的读者可能立刻就想到了,这还不简单,直接修改一下流程文档中不合理的地方然后再次进行流程文档部署并重新启动新的流程实例,这个问题不就迎刃而解了嘛。但是流程文档的部署涉及到流程文档的解析,而流程文档的解析又是一个非常昂贵的操作,仅仅是为了修改节点的名称就需要重新部署流程文档,难免有点得不偿失吧。如果需要频繁修改任务节点的信息,难道每次都需要重新部署流程文档吗?这样未免也太恐怖了吧,想想该方案就有点不合理。很显然重新部署流程文档的解决方案只能解决燃眉之急,并不能从根本上解决该问题,该方式终归不是一个最优的解决方案。基于上述的一些问题,节点缓存类ProcessDefinitionInfoCache应运而生,该类负责管理流程文档中节点(任务节点、服务任务等)的定义信息。该缓存类同其它的缓存类一样需要交给DeploymentManager类进行管理,如代码清单x-所示。其内部使用Map集合进行缓存数据的管理,虽然解决了缓存节点定义信息的问题,但是对于开发人员来说还是存在如下两个弊端。
- 缓存插入以及更新问题。
Activiti引擎并没有为我们提供更新节点缓存的方法,流程文档部署的时候会对节点缓存数据进行初始化,如果缓存丢失,则启动流程实例的时候,会再次执行流程文档的解析操作,从而引起节点缓存的重新添加操作。比如首先根据流程定义id值从ACT_PROCDEF_INFO表中查询当前的流程定义是否存在节点缓存,如果存在则开始从map集合中取出缓存中的数据,并对两者的数据进行比对,如果两者数据不一致,则需要将两者的数据进行合并,然后开始更新map集合以及数据库,如果流程定义缓存存在,则不会触发以上解析的操作。从上面我们可以看出,节点缓存的操作大大增加了客户端使用者操作的复杂度。
- 缓存持久化。
很显然Map数据结构中的数据仅仅是存储在内存中,系统重启或者宕机则缓存丢失。在实际项目开发中,如果我们不能容忍缓存数据的丢失,则可以使用Redis框架进行缓存数据持久化。
从代码清单x-中#-3可以看出想要自定义节点缓存类,则需要设置processDefinitionInfoCache属性值,并且该属性类型为ProcessDefinitionInfoCache(默认节点缓存类),换而言之,我们自定义的节点缓存类必须继承该类,否则肯定报类型转化错误异常。接下来,我们看下该类的核心代码如代码清单x-所示。
代码清单x-ProcessDefinitionInfoCache.java
---------------------------------------------------------------------------------------------------------------------------
protected Map<String, ProcessDefinitionInfoCacheObject> cache;
protected CommandExecutor commandExecutor;
public ProcessDefinitionInfoCache(CommandExecutor commandExecutor) { #-1
this.commandExecutor = commandExecutor;
this.cache = new HashMap<String, ProcessDefinitionInfoCacheObject>();
}
public ProcessDefinitionInfoCache(CommandExecutor commandExecutor, final int limit) {#-2
this.commandExecutor = commandExecutor;
this.cache = Collections.synchronizedMap(new LinkedHashMap<String, ProcessDefinitionInfoCacheObject>(limit + 1, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Map.Entry<String, ProcessDefinitionInfoCacheObject> eldest) {
boolean removeEldest = size() > limit;
return removeEldest;
}
});
}
public ProcessDefinitionInfoCacheObject get(final String processDefinitionId) { #-3
ProcessDefinitionInfoCacheObject infoCacheObject = null;
if (cache.containsKey(processDefinitionId)) {
infoCacheObject = commandExecutor.execute(new Command<ProcessDefinitionInfoCacheObject>() {
public ProcessDefinitionInfoCacheObject execute(CommandContext commandContext) {
ProcessDefinitionInfoEntityManager infoEntityManager = commandContext.getProcessDefinitionInfoEntityManager(); #-4
ObjectMapper objectMapper = commandContext.getProcessEngineConfiguration().getObjectMapper(); #-5
ProcessDefinitionInfoCacheObject cacheObject = cache.get(processDefinitionId);#-6
ProcessDefinitionInfoEntityinfoEntity=infoEntityManager.findProcessDefinitionInfoByProcessDefinitionId(processDefinitionId); #-7
if (infoEntity != null && infoEntity.getRevision() != cacheObject.getRevision()) {
cacheObject.setRevision(infoEntity.getRevision());
if (infoEntity.getInfoJsonId() != null) {
byte[] infoBytes = infoEntityManager.findInfoJsonById(infoEntity.getInfoJsonId());
try { #-8
ObjectNode infoNode = (ObjectNode) objectMapper.readTree(infoBytes);
cacheObject.setInfoNode(infoNode);
} catch (Exception e) {}
}
} else if (infoEntity == null) { #-9
cacheObject.setRevision(0);
cacheObject.setInfoNode(objectMapper.createObjectNode());
}
return cacheObject;
}
});
}
return infoCacheObject;
}
---------------------------------------------------------------------------------------------------------------------------
下面分析一下ProcessDefinitionInfoCache类,并从中解释它为我们提供的功能。
- 成员变量
该类持有cache和commandExecutor两个成员变量,cache集合为存储缓存数据的容器,并提供了针对cache集合的添加、删除等方法。commandExecutor命令执行器,为#-3中函数执行自定义命令服务,稍后的章节我们进行讲解,在这里读者有个简单印象即可。
- 构造函数
该类有两个构造函数,如果对缓存容器的大小不进行限制,则使用#-1构造函数,否则使用#-2函数(LRU算法控制容器大小),不管使用哪个构造函数,都需要传入命令执行器commandExecutor参数,由于该类中并没有定义无参构造函数,因此我们自定义节点缓存类继承该类的同时,必须重写以上两个构造函数中的任意一个,这就意味着我们必须要想尽一切办法获取到命令执行器commandExecutor对象才能保证父类的get函数可以正常使用。
- 获取缓存。
缓存数据的获取工作比较简单,首先根据流程定义id值从cache集合中获取值,如果获取到值则直接返回,否则委托命令执行器执行如下缓存获取逻辑。
- #-4获取流程定义信息实体管理器,该管理器负责管理ACT_PROCDEF_INFO表。
- Activiti默认使用Jackson 类库处理 JSON 格式数据。#-5获取ObjectMapper对象,ObjectMapper是Jackson库的核心类,该类提供一些功能可以将java对象与JSON数据之间进行相互转换,该类内部使用JsonParser和JsonGenerator的实例实现JSON数据的读或者写操作。关于Jackson类库的更多知识读者可以自行查找资料进行学习。
- #-6从缓存中获取节点定义信息。#-7根据流程定义id从数据库表ACT_PROCDEF_INFO查询节点定义信息。
- ProcessDefinitionInfoCacheObject实例对象属性填充。
如果#-7中的infoEntity对象有值,并且该对象的版本信息与缓存中的数据版本信息不一致,则需要将该对象的infoJsonId字符串值转化为byte数组,并通过objectMapper相关方法将byte数组转化为ObjectNode对象,最终将该属性填充到cacheObject对象中。否则执行#-9代码块。
技术团队支持:盘古BPM工作流平台
具体效果参考盘古BPM