Activiti7将默认缓存替换为Redis

 


前言

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,大致流程如下:

  1. 自定义ActivitiDeploymentCache类并实现 DeploymentCache类,重写相关方法。
  2. 序列化ProcessDefinitionCacheEntry,并保存到redis。
  3. 注入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缓存,初步测试可以正常使用,具体情况待进行稳定性测试。

 

 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值