实现参照博客Activiti7将默认缓存替换为Redis_coderYXF的专栏-CSDN博客在其基础上进行调整
目录
1. 前言
因为项目中要求需要将服务分布式部署,导致Activiti默认实现的缓存方法(DefaultDeploymentCache是将缓存存到本地的Map中)这就导致,多台部署的话,可能发生缓存数据的不同步,导致审批出现错误。使用redis进行管理,则能够解决同步问题。
可能发生的错误:点击流程发布或者临时修改Process类的节点连接,无法同步至其它服务
2. Activiti6及以上版本的区别
Activiti5缓存的对象实现了Serializable类,可以直接将对象存储进redis中,不用再做特殊处理。
Activiti6及以上版本缓存的对象中BpmnModel与Process均为实现实例化接口,则需要做进一步的处理。我是使用了Kyro框架进行了序列化处理
3. 编写Kyro工具类
3.1 自定义默认的序列化策略类
Activiti6、7中存在以transient修饰的属性。Kyro默认的序列化类是FieldSerializer,默认不会去序列化被transient修饰的属性,这就导致数据在存取的期间,出现数据的丢失。
FieldSerializer.FieldSerializerConfig.serializeTransient的属性默认为false,将其设置为true则会去序列化被transient修饰的属性。
创建com.esotericsoftware.kryo.serializers.CustomSerializer类
集成自FieldSerializer类,只是在其调用构造方法后,手动在设置一下serializeTransient值为true
package com.esotericsoftware.kryo.serializers;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
public class CustomSerializer<T> extends FieldSerializer<T> {
public CustomSerializer(Kryo kryo, Class type) {
super(kryo, type, (Class[])null);
setSerializeTransient(true);
}
public CustomSerializer(Kryo kryo, Class type, Class[] generics) {
super(kryo, type, generics, kryo.getFieldSerializerConfig().clone());
setSerializeTransient(true);
}
protected CustomSerializer(Kryo kryo, Class type, Class[] generics, FieldSerializerConfig config) {
super(kryo, type, generics, config);
setSerializeTransient(true);
}
}
ps:FieldSerializer中的序列化方法write
public void write(Kryo kryo, Output output, T object) {
if (Log.TRACE) {
Log.trace("kryo", "FieldSerializer.write fields of class: " + object.getClass().getName());
}
if (this.config.isOptimizedGenerics()) {
if (this.typeParameters != null && this.generics != null) {
this.rebuildCachedFields();
}
if (this.genericsScope != null) {
kryo.getGenericsResolver().pushScope(this.type, this.genericsScope);
}
}
FieldSerializer.CachedField[] fields = this.fields;
int i = 0;
int n;
for(n = fields.length; i < n; ++i) {
fields[i].write(output, object);
}
// 判断了是否序列化transient修饰的方法
if (this.config.isSerializeTransient()) {
i = 0;
for(n = this.transientFields.length; i < n; ++i) {
this.transientFields[i].write(output, object);
}
}
if (this.config.isOptimizedGenerics() && this.genericsScope != null) {
kryo.getGenericsResolver().popScope();
}
}
3.2 序列化工具类
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.CustomSerializer;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.poi.ss.formula.functions.T;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.stereotype.Component;
import sun.reflect.ReflectionFactory;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.concurrent.ConcurrentHashMap;
/**
* 序列化工具类
*
* @author 36020
*/
@Slf4j
@Component
public class SerializeUtils {
private final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> {
// kyro是线程不安全的,需要为每个线程创建一个独立的对象
Kryox kryo = new Kryox();
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(
new StdInstantiatorStrategy()));
kryo.setDefaultSerializer(CustomSerializer.class);
return kryo;
});
public byte[] serialize(Object obj) {
if (obj == null) {
return null;
}
Kryo kryo = kryoLocal.get();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream);
kryo.writeClassAndObject(output, obj);
output.close();
return byteArrayOutputStream.toByteArray();
}
public Object deSerialize(byte[] bytes) {
if (bytes == null) {
return null;
}
Kryo kryo = kryoLocal.get();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream);
input.close();
return kryo.readClassAndObject(input);
}
/**
* Kyro序列化没有无参构造方法的类时会报错,所以进行改造
*/
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);
}
}
}
}
4. 自定义缓存实现
实现DeploymentCache
版本5中泛型为ProcessDefinitionEntity
版本6和7泛型为ProcessDefinitionCacheEntry
@Component
public class CustomDeploymentCache implements DeploymentCache<ProcessDefinitionCacheEntry> {
private final static RedisService redisService;
private final static String ACTIVITI_CACHE = "activiti_cache:";
private final static String BPMN_CACHE = "bpmn_cache:";
private final static String PROCESS_CACHE = "process_cache:";
@Resource
private SerializeUtils serializeUtils;
static {
redisService = SpringUtils.getBean(RedisService.class);
// 启动时删除全部缓存
clearAll();
}
@Override
public ProcessDefinitionCacheEntry get(String id) {
byte[] entry = redisService.getCacheMapValue(ACTIVITI_CACHE, id);
byte[] bpmn = redisService.getCacheMapValue(BPMN_CACHE, id);
byte[] process = redisService.getCacheMapValue(PROCESS_CACHE, id);
if (entry == null) {
return null;
}
return new ProcessDefinitionCacheEntry((ProcessDefinition)serializeUtils.deSerialize(entry)
, (BpmnModel) serializeUtils.deSerialize(bpmn)
, (Process) serializeUtils.deSerialize(process));
}
@Override
public void add(String id, ProcessDefinitionCacheEntry entry) {
redisService.setCacheMapValue(ACTIVITI_CACHE, id, serializeUtils.serialize(entry.getProcessDefinition()));
redisService.setCacheMapValue(BPMN_CACHE, id, serializeUtils.serialize(entry.getBpmnModel()));
redisService.setCacheMapValue(PROCESS_CACHE, id, serializeUtils.serialize(entry.getProcess()));
}
@Override
public void remove(String id) {
redisService.delCacheMapValue(ACTIVITI_CACHE, id);
redisService.delCacheMapValue(BPMN_CACHE, id);
redisService.delCacheMapValue(PROCESS_CACHE, id);
}
@Override
public void clear() {
clearAll();
}
public static void clearAll() {
redisService.deleteObject(ACTIVITI_CACHE);
redisService.deleteObject(BPMN_CACHE);
redisService.deleteObject(PROCESS_CACHE);
}
@Override
public boolean contains(String id) {
return redisService.getCacheMapValue(ACTIVITI_CACHE, id) != null;
}
}
5. 切换Activiti默认缓存实现类
@Bean
public DeploymentCache<ProcessDefinitionCacheEntry> deploymentCache() {
return new CustomDeploymentCache();
}
/**
* 初始化配置
*
* @return
*/
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
transactionManager.setDataSource(dataSource);
configuration.setTransactionManager(transactionManager);
// 执行工作流对应的数据源
configuration.setDataSource(dataSource);
// 是否自动创建流程引擎表
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
configuration.setAsyncExecutorActivate(false);
// 修改缓存
configuration.setProcessDefinitionCache(deploymentCache());
// 流程历史等级
configuration.setHistoryLevel(HistoryLevel.AUDIT);
return configuration;
}