1. 问题
参考几篇文章,Activiti7修改内部缓存为redis,activiti流程缓存更换默认实现为Redis,Activiti7将默认缓存替换为Redis, 按照他们说的,本地执行都不能执行成功。
ACTIVIT 5.15.1修改记录 这篇写的很随意,可能他不知道Kryo这个专门序列化和反序列化的工具,类无需实现接口Serializable,transient 属性也可以序列化
activiti流程定义序列化和反序列化不能是json,会丢失数据;jdk序列化也不可以,必须实现接口Serializable才能序列化。
Kryo框架都满足。
2. Kryo
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.45</version>
</dependency>
注意:kryo版本,5的可以用Pool,4的只能用ThreadLocal解决线程不安全,kryo-serializers 这个包提供了一些序列化类,sun.misc.Unsafe,sun.reflect.ReflectionFactory sun包内类,可以实现无构造器创建对象,如接口,抽象类
3. activiti5+kryo+redis
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>5.23.0</version>
</dependency>
<dependency>
groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
redis配置
package com.yl.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.yl.kryo.KryoRedisSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
import java.time.Duration;
/**
* redis配置类
*
* @author liuxb
* @date 2022/7/17 15:56
*/
@Configuration
public class RedisConfig {
private final StringRedisSerializer keySerializer = new StringRedisSerializer();
private final Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
private final RedisSerializer<Object> kryoRedisSerializer = new KryoRedisSerializer<>();
/**
* 原生的配置,配置key,value的序列化方式
*/
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//序列化输入的类型,存储到redis里的数据将是有类型的json数据
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jsonRedisSerializer.setObjectMapper(mapper);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setValueSerializer(jsonRedisSerializer);
//hash key value 序列化
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 使用kryo序列化/反序列化 set/hset操作
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> kryoRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// key序列化
redisTemplate.setKeySerializer(keySerializer);
// value序列化
redisTemplate.setValueSerializer(kryoRedisSerializer);
// Hash key序列化
redisTemplate.setHashKeySerializer(keySerializer);
// Hash value序列化
redisTemplate.setHashValueSerializer(kryoRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
KryoRedisSerializer
package com.yl.kryo;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.SerializerFactory;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import de.javakaffee.kryoserializers.CopyForIterateCollectionSerializer;
import de.javakaffee.kryoserializers.SynchronizedCollectionsSerializer;
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.impl.bpmn.behavior.BpmnActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.el.JuelExpression;
import org.activiti.engine.impl.javax.el.ValueExpression;
import org.activiti.engine.impl.juel.Bindings;
import org.activiti.engine.impl.juel.Builder;
import org.activiti.engine.impl.juel.TreeValueExpression;
import org.activiti.engine.impl.juel.TypeConverterImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Redis的Kryo序列化实现类
*
* @author liuxb
* @date 2022/12/19 16:57
*/
@Slf4j
public class KryoRedisSerializer<T> implements RedisSerializer<T> {
/**
* 由于 Kryo 不是线程安全的。每个线程都应该有自己的 Kryo,Input 或 Output 实例。
* 所以,使用 ThreadLocal 存放 Kryo 对象
* 这样减少了每次使用都实例化一次 Kryo 的开销又可以保证其线程安全
*/
private static final ThreadLocal<Kryo> KRYO_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
// 设置循环引用
kryo.setReferences(true);
// 设置序列化时对象是否需要设置对象类型,默认true,需要注册类
kryo.setRegistrationRequired(false);
SerializerFactory.FieldSerializerFactory factory = new SerializerFactory.FieldSerializerFactory();
factory.getConfig().setSerializeTransient(true);
//serializeTransient设置为true
kryo.setDefaultSerializer(factory);
//kryo-serializers包解决只读的Collection和Map
UnmodifiableCollectionsSerializer.registerSerializers(kryo);
//Collection和Map同步
SynchronizedCollectionsSerializer.registerSerializers(kryo);
//See {@link StdInstantiatorStrategy} to * create objects via without calling any constructor 创建对象无需构造器
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
return kryo;
});
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
@Override
public byte[] serialize(Object t) throws SerializationException {
if (t == null) {
return EMPTY_BYTE_ARRAY;
}
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos)) {
Kryo kryo = KRYO_THREAD_LOCAL.get();
// 对象的 Class 信息一起序列化
kryo.writeClassAndObject(output, t);
KRYO_THREAD_LOCAL.remove();
return output.toBytes();
} catch (Exception e) {
throw new SerializationException("Could not write byte[]: " + e.getMessage(), e);
}
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Input input = new Input(bais)) {
Kryo kryo = KRYO_THREAD_LOCAL.get();
// 通过存储在字节数组中的 Class 信息来确定反序列的类型
Object object = kryo.readClassAndObject(input);
KRYO_THREAD_LOCAL.remove();
return (T) object;
} catch (IOException e) {
throw new SerializationException("Could not read byte[]: " + e.getMessage(), e);
}
}
}
KryoConstant
package com.yl.kryo;
/**
* redis中保存的kryo序列化后的activiti流程定义缓存
*
* @author liuxb
* @date 2022/12/22 11:19
*/
public class KryoConstant {
public final static String ACTIVITI_CACHE = "activiti_cache:";
public final static String BPMN_CACHE = "bpmn_cache:";
public final static String PROCESS_CACHE = "process_cache:";
}
KryoRedisUtil 封装工具类
package com.yl.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* KryoRedisTemplate
*
* @author liuxb
* @date 2022/12/21 9:05
*/
@Slf4j
@Component
public class KryoRedisUtil {
@Qualifier("kryoRedisTemplate")
@Autowired
private RedisTemplate kryoRedisTemplate;
/**
* key前缀
*/
@Value("${spring.redis.keyPrefix}:")
private String prefix;
/**
* 根据key获取值
*
* @param key 键
* @return 值
*/
public Object get(String key) {
log.info("redis查询流程定义数据, key:{}", this.prefix + key);
return key == null ? null : kryoRedisTemplate.opsForValue().get(this.prefix + key);
}
/**
* 将值放入缓存
*
* @param key 键
* @param value 值
* @return true成功 false 失败
*/
public void set(String key, Object value) {
log.info("redis添加流程定义数据, key:{}", this.prefix + key);
kryoRedisTemplate.opsForValue().set(this.prefix + key, value);
}
/**
* 删除key
*
* @param key
* @return
*/
public boolean deleteKey(String key) {
log.info("redis删除流程定义数据, key:{}", this.prefix + key);
return kryoRedisTemplate.delete(this.prefix + key);
}
/**
* 删除keys
*
* @param keys
* @return
*/
public boolean deleteKeys(Collection<String> keys) {
List<String> collect = keys.stream().map(key -> key.startsWith(this.prefix) ? key :this.prefix + key).collect(Collectors.toList());
return kryoRedisTemplate.delete(collect) == collect.size();
}
/**
* 删除key like
*
* @param key
* @return
*/
public Long deleteKeyLike(String key) {
if (StringUtils.isBlank(key)) {
return 0L;
}
Set<String> keys = kryoRedisTemplate.keys(this.prefix + key + "*");
if (keys.isEmpty()) {
return 0L;
}
log.info("redis删除全部流程定义数据, key:{}, 共{}条", this.prefix + key, keys.size());
return deleteKeys(keys) ? keys.size() : 0L;
}
/**
* 获取指定前缀的key集合
*
* @param key
* @return
*/
public List<String> getKeys(String key) {
List<String> list = new ArrayList<>();
if (StringUtils.isBlank(key)) {
return list;
}
Set<String> keys = kryoRedisTemplate.keys(this.prefix + key + "*");
if (keys.isEmpty()) {
return list;
}
log.info("redis中全部流程定义数据, key:{}, 共{}条", this.prefix + key, keys.size());
list.addAll(keys);
return list;
}
}
ActivitiDeploymentCache
package com.yl.kryo;
import com.yl.util.KryoRedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.event.impl.ActivitiEventSupport;
import org.activiti.engine.impl.persistence.deploy.DeploymentCache;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
/**
* 使用redis保存activiti流程定义缓存,以hash方式存储
*
* @author liuxubo
* @date 2022/12/18 21:51
*/
@Slf4j
@Component
public class ActivitiDeploymentCache implements DeploymentCache<ProcessDefinitionEntity> {
@Autowired
private KryoRedisUtil kryoRedisUtil;
/**
* redis缓存中根据流程定义id,查询流程定义对象
*
* @param procDefId 流程定义id
* @return
*/
@Override
public ProcessDefinitionEntity get(String procDefId) {
ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) kryoRedisUtil.get(KryoConstant.ACTIVITI_CACHE + procDefId);
if (processDefinitionEntity == null) {
return null;
}
//processDefinitionEntity内属性 protected transient ActivitiEventSupport eventSupport; CopyOnWriteArrayList 无法序列化
// public ActivitiEventSupport() {
// eventListeners = new CopyOnWriteArrayList<ActivitiEventListener>();
// typedListeners = new HashMap<ActivitiEventType, List<ActivitiEventListener>>();
// }
// 反序列化时 CopyOnWriteArrayList 无法处理,bpmnModel 需要手动添加 ActivitiEventSupport 否则空指针
// eventSupport 只有ge方法,没有set方法,可以通过反射赋值
Field field = ReflectionUtils.findField(ProcessDefinitionEntity.class, "eventSupport");
field.setAccessible(true);
try {
field.set(processDefinitionEntity, new ActivitiEventSupport());
} catch (IllegalAccessException e) {
log.error("反射设置属性eventSupport异常", e);
}
return processDefinitionEntity;
}
/**
* redis缓存中添加流程定义id和查询流程定义对象,作为hash
*
* @param procDefId 流程定义id
* @param processDefinitionEntity
*/
@Override
public void add(String procDefId, ProcessDefinitionEntity processDefinitionEntity) {
kryoRedisUtil.set(KryoConstant.ACTIVITI_CACHE + procDefId, processDefinitionEntity);
}
/**
* redis缓存中删除流程定义id
*
* @param procDefId 流程定义id
*/
@Override
public void remove(String procDefId) {
kryoRedisUtil.deleteKey(KryoConstant.ACTIVITI_CACHE + procDefId);
}
/**
* 删除redis缓存中全部activiti流程定义
*/
@Override
public void clear() {
kryoRedisUtil.deleteKeyLike(KryoConstant.ACTIVITI_CACHE);
}
/**
* 查询redis缓存中activiti流程定义的个数
*
* @return
*/
public int size() {
int number = kryoRedisUtil.getKeys(KryoConstant.ACTIVITI_CACHE).size();
return number;
}
}
acitviti配置
package com.yl.config;
import com.yl.kryo.ActivitiDeploymentCache;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* ActivitiConfig
*
* @author liuxb
* @date 2022/12/19 8:31
*/
@Configuration
public class ActivitiConfig implements ProcessEngineConfigurationConfigurer {
@Autowired
private ActivitiDeploymentCache activitiDeploymentCache;
@Override
public void configure(SpringProcessEngineConfiguration configuration) {
configuration.setLabelFontName("宋体");
configuration.setActivityFontName("宋体");
// 是否自动创建流程引擎表
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
configuration.setAsyncExecutorActivate(false);
//注册自定义的redis流程定义缓存类
configuration.setProcessDefinitionCache(activitiDeploymentCache);
// 流程历史等级
configuration.setHistoryLevel(HistoryLevel.AUDIT);
// configuration.setHistory("audit");
configuration.setDbIdentityUsed(false);
}
}
注意:这里用set/get保存序列化数据,也可以用hset/hget比较好
4. 测试
activiti5-server:activiti_cache:vacation:1:4 redis中的key
启动流程
http://localhost:8081/activiti5/process/startProcInstByKey
{
"procDefKey": "vacation",
"startUserId": "123456",
"variables":{
"applySubmit": "zhangsan"
}
}
{
"retCode": "0000",
"message": "请求成功",
"result": {
"procInstId": "80001",
"procDefId": "vacation:1:4",
"procDefName": "请假流程",
"procDefKey": "vacation",
"deployId": "1",
"busKey": "vacation1234562022122214571313774",
"startTime": "2022-12-22 14:57:16",
"endTime": null,
"duration": null,
"end": false,
"startActivityId": "StartEvent",
"endActivityId": null,
"startUserId": "123456",
"tenantId": "",
"variables": {}
}
}
审批流程
http://localhost:8081/activiti5/task/completeTaskToNext
{
"taskId": "27507",
"approveResultCode": "P",
"remark": "我同意",
"nextAssingeeVarName": "deptManager",
"nextAssingee": "248943",
"rejectFlag": false,
"apply": {
"applySubmitPass": true
}
}
{
"code": "0000",
"msg": "请求成功",
"result": {
"finished": true
}
}
5. 问题总结
问题1.Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain:
org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity["processDefinition"])
不能循环引用
问题2. com.esotericsoftware.kryo.KryoException: Class cannot be created (missing no-arg constructor): org.activiti.engine.impl.pvm.process.ActivityImpl
Serialization trace:
activities (org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity)
反序列化,类中没有无参构造,无法创建类
问题3.org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.InstantiationError: org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity
Caused by: java.lang.InstantiationError: org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity
java中抽象类和接口是不能被实例化
问题4. CommandContext -Error while closing command context
java.lang.NullPointerException: null
at org.activiti.engine.delegate.event.impl.ActivitiEventDispatcherImpl.dispatchEvent(ActivitiEventDispatcherImpl.java:82)
activiti5中
ActivitiEventDispatcherImpl.java:82
processDefinition.getEventSupport().dispatchEvent(event);
ProcessDefinitionEntity
protected transient ActivitiEventSupport eventSupport; 属性
参考 kryo-master源码内测试类 test\com\esotericsoftware\kryo\serializers\FieldSerializerTest.java
@Test
void testSerializeTransientsUsingGlobalConfig () {
FieldSerializerFactory factory = new FieldSerializerFactory();
factory.getConfig().setSerializeTransient(true); // 设置true 全局transient属性序列化
kryo.setDefaultSerializer(factory);
}
问题5. 还是报错
CommandContext -Error while closing command context
java.lang.NullPointerException: null
at java.util.concurrent.CopyOnWriteArrayList.size(CopyOnWriteArrayList.java:162)
定位报错是
eventSupport = {ActivitiEventSupport@11149}
eventListeners = {CopyOnWriteArrayList@11165} Unable to evaluate the expression Method threw 'java.lang.NullPointerException' exception.
typedListeners = {HashMap@11169} size = 0
网上说的
将
protected transient ActivitiEventSupport eventSupport;
修改成:
protected ActivitiEventSupport eventSupport;
提供set方法
public void setEventSupport(Object eventSupport) {
this.eventSupport = eventSupport;
}
要修改源码,不可取。
经测试发现,不知道什么原因
public ActivitiEventSupport() {
eventListeners = new CopyOnWriteArrayList<ActivitiEventListener>();
typedListeners = new HashMap<ActivitiEventType, List<ActivitiEventListener>>();
}
内CopyOnWriteArrayList,反序列化时eventListeners 一直为null
经测试,反序列化 processDefinitionEntity时,把属性protected transient ActivitiEventSupport eventSupport;赋值就可以了
由于没有set方法,需要反射
Field field = ReflectionUtils.findField(ProcessDefinitionEntity.class, "eventSupport");
field.setAccessible(true);
try {
field.set(processDefinitionEntity, new ActivitiEventSupport());
} catch (IllegalAccessException e) {
log.error("反射设置属性eventSupport异常", e);
}