前言
Activiti7默认将流程缓存到内存里,默认实现为DefaultDeploymentCache,如下
public class DefaultDeploymentCache<T> implements DeploymentCache<T> {
protected Map<String, T> cache;
/** Cache with no limit */
public DefaultDeploymentCache() {
this.cache = synchronizedMap(new HashMap<String, T>());
}
/**
* Cache which has a hard limit: no more elements will be cached than the limit.
*/
public DefaultDeploymentCache(final int limit) {
this.cache = synchronizedMap(new LinkedHashMap<String, T>(limit + 1, 0.75f, true) { // +1 is needed, because the entry is inserted first, before it is removed
// 0.75 is the default (see javadocs)
// true will keep the 'access-order', which is needed to have a real LRU cache
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Map.Entry<String, T> eldest) {
boolean removeEldest = size() > limit;
if (removeEldest && logger.isTraceEnabled()) {
logger.trace("Cache limit is reached, {} will be evicted", eldest.getKey());
}
return removeEldest;
}
});
}
}
可以看到该缓存用HashMap存储在内存中,且无法设置超时等属性,导致内存越来越大,最终溢出,现将缓存替换为Redis。
一、描述
本案例采用Activiti7+Jedis,大致流程如下:
- 自定义ActivitiDeploymentCache类并实现 DeploymentCache类,重写相关方法。
- 序列化ProcessDefinitionCacheEntry,并保存到redis。
- 注入ActivitiDeploymentCache。
二、操作步骤
1.自定义ActivitiDeploymentCache
@Slf4j
public class ActivitiDeploymentCache implements DeploymentCache<ProcessDefinitionCacheEntry> {
public static final String DEFAULT_ACTIVITI_DEF_CACHE_BEAN_NAME = "activitiDeploymentCache";
private static final String PROCESS_DEFINITION_NAME = "processDefinition";
private static final String BPMN_MODEL_NAME = "BpmnModel";
@Autowired
private JedisHelper jedisHelper;
@Override
public ProcessDefinitionCacheEntry get(String id) {
log.info("ActivitiCacheGet --- id:" + id);
ProcessDefinitionCacheEntry entry = getProcessDefinitionCacheEntry(
jedisHelper.get(getProcessDefinitionId(id).getBytes()), jedisHelper.get(getBpmnId(id).getBytes()));
return entry;
}
@Override
public boolean contains(String id) {
return jedisHelper.hasKey(getProcessDefinitionId(id).getBytes())
&& jedisHelper.hasKey(getBpmnId(id).getBytes());
}
@SneakyThrows
@Override
public void add(String id, ProcessDefinitionCacheEntry entry) {
log.info("ActivitiCacheAdd --- id:" + id);
/**
* // activiti自带Converter,缺失event等属性
*
* BpmnJsonConverter util = new BpmnJsonConverter(); ObjectNode node =
* util.convertToJson(object.getBpmnModel()); CustomBpmnJsonConverter
* converter = new CustomBpmnJsonConverter(); ObjectMapper mapper = new
* ObjectMapper(); jedisHelper.set(id + "bpmModel",
* mapper.writeValueAsString(node));
**/
jedisHelper.set(getProcessDefinitionId(id).getBytes(),
KryoUtil.serialize(entry.getProcessDefinition(), ProcessDefinitionEntityImpl.class));
jedisHelper.set(getBpmnId(id).getBytes(), KryoUtil.serialize(entry.getBpmnModel()));
}
@Override
public void remove(String id) {
jedisHelper.del(getProcessDefinitionId(id).getBytes(), getBpmnId(id).getBytes());
}
/**
*
* 防止出错,不实现
*
* @author yang.xuefeng
* @date 2021/05/12
* @return void
*/
@Override
public void clear() {
// jedisHelper.clearAll();
}
private ProcessDefinitionCacheEntry getProcessDefinitionCacheEntry(byte[] processBytes, byte[] bpmnBytes) {
/**
* // activity自带Converter,反序列化丢失属性,弃用
*
* ObjectMapper mapper = new ObjectMapper(); ObjectNode node =
* (ObjectNode) mapper.readTree(bpmStr); log.info("getNode -- " + node);
* BpmnModel model = util.convertToBpmnModel(node);
* model.getMainProcess()
* .setInitialFlowElement(model.getMainProcess().getFlowElements().stream().findFirst().get());
**/
if (null == processBytes || null == bpmnBytes)
return null;
ProcessDefinitionEntityImpl processDefinition = KryoUtil.deserialize(processBytes,
ProcessDefinitionEntityImpl.class);
BpmnModel bpmnModel = KryoUtil.deserialize(bpmnBytes);
Process process = bpmnModel.getMainProcess();
ProcessDefinitionCacheEntry entity = new ProcessDefinitionCacheEntry(processDefinition, bpmnModel, process);
return entity;
}
String getProcessDefinitionId(String id) {
return id + PROCESS_DEFINITION_NAME;
}
String getBpmnId(String id) {
return id + BPMN_MODEL_NAME;
}
}
2.序列化ProcessDefinitionCacheEntry详解
activiti6开始,流程信息缓存对象更改为ProcessDefinitionCacheEntry
public class ProcessDefinitionCacheEntry implements Serializable {
protected ProcessDefinition processDefinition;
protected BpmnModel bpmnModel;
protected Process process;
Getter/Setter...
}
其中ProcessDefinition是接口、BpmnModel没有实现序列化接口,无法用常见的FastJson和Serialize工具实现序列化。经过查看源码,发现了activiti自带BpmnJsonConverter,但是经过实验发现依然会丢失相应数据,各种尝试后,发现Kryo可以很好的实现序列化/反序列化。以下KryoUtil相关代码。
public class KryoUtil {
/**
*
* 线程安全
*
* @param null
* @author yang.xuefeng
* @date 2021/05/12
* @return
*/
private static final ThreadLocal<Kryox> kryoLocal = new ThreadLocal<Kryox>() {
@Override
protected Kryox initialValue() {
Kryox kryo = new Kryox();
// 默认值为true,是否开启循环引用
kryo.setReferences(true);
// 默认值为false,是否需要手动注册对象
kryo.setRegistrationRequired(false);
// UnmodifiableCollectionsSerializer.registerSerializers(kryo);
return kryo;
}
};
/**
*
* 序列化
*
* @param obj
* @author yang.xuefeng
* @date 2021/05/08
* @return byte[]
*/
public static byte[] serialize(Object obj) {
if (obj == null) {
return null;
}
Kryox kryo = kryoLocal.get();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream);
kryo.writeClassAndObject(output, obj);
output.close();
return byteArrayOutputStream.toByteArray();
}
/**
*
* 反序列化
*
* @param bytes
* @author yang.xuefeng
* @date 2021/05/08
* @return T
*/
public static <T> T deserialize(byte[] bytes) {
if (bytes == null) {
return null;
}
Kryox kryo = kryoLocal.get();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream);
input.close();
return (T) kryo.readClassAndObject(input);
}
}
使用该Util序列化某些对象时会报错,例如没有无参构造方法的对象,于是重写相关配置
public class Kryox extends Kryo {
private final ReflectionFactory REFLECTION_FACTORY = ReflectionFactory.getReflectionFactory();
private final ConcurrentHashMap<Class<?>, Constructor<?>> _constructors = new ConcurrentHashMap<Class<?>, Constructor<?>>();
@Override
public <T> T newInstance(Class<T> type) {
try {
return super.newInstance(type);
} catch (Exception e) {
return (T) newInstanceFromReflectionFactory(type);
}
}
private Object newInstanceFrom(Constructor<?> constructor) {
try {
return constructor.newInstance();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public <T> T newInstanceFromReflectionFactory(Class<T> type) {
Constructor<?> constructor = _constructors.get(type);
if (constructor == null) {
constructor = newConstructorForSerialization(type);
Constructor<?> saved = _constructors.putIfAbsent(type, constructor);
if (saved != null)
constructor = saved;
}
return (T) newInstanceFrom(constructor);
}
private <T> Constructor<?> newConstructorForSerialization(Class<T> type) {
try {
Constructor<?> constructor = REFLECTION_FACTORY.newConstructorForSerialization(type,
Object.class.getDeclaredConstructor());
constructor.setAccessible(true);
return constructor;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
至此Kryo工具可以正常使用,接下来利用KryoUtil对ProcessDefinitionCacheEntry进行序列化/反序列化,发现流程能成功部署并存进redis了,但是启动流程时发现Activiti报错
Error while evaluating expression: ${defaultServiceTaskBehavior}
经过很久debug,发现是TreeValueExpression中node属性问题,该属性使用了transient关键字,导致该属性不会被序列化,于是覆盖此类,取消transient关键字
package de.odysseus.el;
public final class TreeValueExpression extends ValueExpression {
...
/**
* transient会忽略序列化/反序列化
*/
// private transient ExpressionNode node;
private ExpressionNode node;
...
}
至此序列化/反序列化完毕,开始存入redis。
ps:存储对象优化为CustomProcessDefinitionCacheEntry
@Data
public class CustomProcessDefinitionCacheEntry {
private ProcessDefinitionEntityImpl processDefinition;
private BpmnModel bpmnModel;
public ProcessDefinitionCacheEntry convertToProcessDefinitionCacheEntry() {
ProcessDefinitionCacheEntry entry = new ProcessDefinitionCacheEntry(this.getProcessDefinition(),
this.getBpmnModel(), this.getBpmnModel().getMainProcess());
return entry;
}
}
3.存入Redis
这里直接贴JedisHelper代码,JedisPoolHelper为自定义线程池,也可以不用线程池直接使用new Jedis()
public class JedisHelper {
public static final String DEFAULT_JEDIS_HELPER_BEAN_NAME = "ibrpaJedisHelper";
@Autowired
private JedisPoolHelper jedisPoolHelper;
private Jedis jedis = null;
/** -------------byte[] start------------- **/
/**
*
* 查询key是否存在
*
* @param key
* @author yang.xuefeng
* @date 2021/04/29
* @return boolean
*/
public boolean hasKey(byte[] key) {
try {
jedis = jedisPoolHelper.getJedis();
return jedis.exists(key);
} catch (Exception e) {
log.error("{} hasKey(byte[]) error,", getClass().getName(), e);
e.printStackTrace();
} finally {
jedisPoolHelper.close(jedis);
}
return false;
}
/**
*
* 存储byte数组
*
* @param key
* @param bytes
* @author yang.xuefeng
* @date 2021/05/08
* @return boolean
*/
public boolean set(byte[] key, byte[] bytes) {
try {
jedis = jedisPoolHelper.getJedis();
jedis.set(key, bytes);
return true;
} catch (Exception e) {
log.error("{} set(byte[], byte[]) error,", getClass().getName(), e);
return false;
} finally {
jedisPoolHelper.close(jedis);
}
}
/**
* 获取byte[] Value
*
* @param key
* @author yang.xuefeng
* @date 2021/05/12
* @return byte[]
*/
public byte[] get(byte[] key) {
try {
jedis = jedisPoolHelper.getJedis();
return jedis.get(key);
} catch (Exception e) {
log.error("{} get(byte[]) error,", getClass().getName(), e);
return null;
} finally {
jedisPoolHelper.close(jedis);
}
}
/**
* 删除byte[] Key
*
* @param key
* @author yang.xuefeng
* @date 2021/05/12
* @return java.lang.Long
*/
public Long del(byte[]... key) {
try {
jedis = jedisPoolHelper.getJedis();
return jedis.del(key);
} catch (Exception e) {
log.error("{} del(byte[]...) error,", getClass().getName(), e);
} finally {
jedisPoolHelper.close(jedis);
}
return 0L;
}
}
4.注入CustomDeploymentCache
@Configuration
@Slf4j
@Primary
public class ActivitiDefCacheConfiguration implements ProcessEngineConfigurationConfigurer {
/**
* Spring应用上下文环境
*/
private ApplicationContext applicationContext;
private ExtensionsVariablesMappingProvider variablesMappingProvider;
private ProcessVariablesInitiator processVariablesInitiator;
private EventSubscriptionPayloadMappingProvider eventSubscriptionPayloadMappingProvider;
/**
*
* 构造器注入
*
* @param applicationContext
* @param variablesMappingProvider
* @param processVariablesInitiator
* @param eventSubscriptionPayloadMappingProvider
* @author yang.xuefeng
* @date 2021/05/07
* @return
*/
public ActivitiDefCacheConfiguration(ApplicationContext applicationContext,
ExtensionsVariablesMappingProvider variablesMappingProvider,
ProcessVariablesInitiator processVariablesInitiator,
EventSubscriptionPayloadMappingProvider eventSubscriptionPayloadMappingProvider) {
this.applicationContext = applicationContext;
this.variablesMappingProvider = variablesMappingProvider;
this.processVariablesInitiator = processVariablesInitiator;
this.eventSubscriptionPayloadMappingProvider = eventSubscriptionPayloadMappingProvider;
}
/**
*
* 加载流程redis缓存
*
* @author yang.xuefeng
* @date 2021/05/06
* @return ActivitiDefCache
*/
@Bean(ActivitiDeploymentCache.DEFAULT_ACTIVITI_DEF_CACHE_BEAN_NAME)
@ConditionalOnMissingBean(name = ActivitiDeploymentCache.DEFAULT_ACTIVITI_DEF_CACHE_BEAN_NAME)
public ActivitiDeploymentCache activitiDeploymentCache() {
log.info("加载流程redis缓存配置类[ActivitiDeploymentCache]...");
return new ActivitiDeploymentCache();
}
/**
*
* 自定义activiti引擎配置类
*
* @param processEngineConfiguration
* @author yang.xuefeng
* @date 2021/05/07
* @return void
*/
@Override
public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
processEngineConfiguration.setEventSubscriptionPayloadMappingProvider(eventSubscriptionPayloadMappingProvider);
processEngineConfiguration.setActivityBehaviorFactory(
new MappingAwareActivityBehaviorFactory(variablesMappingProvider, processVariablesInitiator));
processEngineConfiguration.setProcessDefinitionCache(applicationContext.getBean(ActivitiDeploymentCache.class));
// 配置自定义用户任务解析器
// CustomBpmnJsonConverter.initCustomJsonConverter();
}
}
总结
以上就是Activiti7替换redis缓存,初步测试可以正常使用,具体情况待进行稳定性测试。