activiti5使用redis缓存流程定义

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);
        }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值