说明
最近在做的事情就是把 Spring Boot 的项目迁移到 Solon 上来。我们的有一个 OA 系统,里面使用到了 Flowable 6.5。照例我还是从官网入手,其中说到是不需要适配可以直接使用。然后附带了例子,https://gitee.com/hiro/flowable-solon-web。
看起来还是比较简单,但也没那么简单,还好之前有看网友提供了 easy-flowable,https://gitee.com/iajie/easy-flowable,中间也有请教作者几个问题,感谢。如果是新项目推荐使用,easy-flowable 不仅提供了封装和增强,还提供了不错的 UI编排界面,使得 flowable 更简单易用。
在统一看了相关的代码之后,主要的适配工作,就是根据配置设置ProcessEngineConfiguration, 并创建 ProcessEngine 及相关的 Service 的 bean 对象。
方法
在我们的系统中为一些基础组件采用 Solon 插件的方式封装一部分初始化工作。
配置
/**
* @author airhead
*/
@Configuration
@Inject(value = "${flowable}", autoRefreshed = true)
@Data
public class FlowableProperties {
private String databaseSchemaUpdate = "true";
private Boolean asyncExecutorActivate = false;
private String historyLevel = "audit";
}
构建 Bean
根据配置设置ProcessEngineConfiguration, 并创建 ProcessEngine 及相关的 Service 的 bean 对象。
public class XPluginImp implements Plugin {
private AppContext appContext;
@Override
public void start(AppContext context) throws Throwable {
appContext = context;
appContext.beanScan(FlowableProperties.class);
appContext.getBeanAsync(
DataSource.class,
dataSource -> {
FlowableProperties flowableProperties = appContext.getBean(FlowableProperties.class);
ProcessEngineConfigurationImpl engineConfiguration =
(ProcessEngineConfigurationImpl)
ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();
engineConfiguration.setDataSource(dataSource);
engineConfiguration.setDatabaseSchemaUpdate("false");
// 设置流程历史级别
engineConfiguration.setHistoryLevel(
HistoryLevel.getHistoryLevelForKey(flowableProperties.getHistoryLevel()));
// 设置表达式管理器
ExpressionManager ficusExpressionManager = new SolonExpressionManager(appContext, null);
engineConfiguration.setExpressionManager(ficusExpressionManager);
// 定时任务开关
engineConfiguration.setAsyncExecutorActivate(
flowableProperties.getAsyncExecutorActivate());
appContext.wrapAndPut(ProcessEngineConfiguration.class, engineConfiguration);
ProcessEngine processEngine = engineConfiguration.buildProcessEngine();
appContext.wrapAndPut(ProcessEngine.class, processEngine);
RuntimeService runtimeService = processEngine.getRuntimeService();
appContext.wrapAndPut(RuntimeService.class, runtimeService);
RepositoryService repositoryService = processEngine.getRepositoryService();
appContext.wrapAndPut(RepositoryService.class, repositoryService);
IdentityService identityService = processEngine.getIdentityService();
appContext.wrapAndPut(IdentityService.class, identityService);
TaskService taskService = processEngine.getTaskService();
appContext.wrapAndPut(TaskService.class, taskService);
HistoryService historyService = processEngine.getHistoryService();
appContext.wrapAndPut(HistoryService.class, historyService);
ManagementService managementService = processEngine.getManagementService();
appContext.wrapAndPut(ManagementService.class, managementService);
FormService formService = processEngine.getFormService();
appContext.wrapAndPut(FormService.class, formService);
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
appContext.wrapAndPut(DynamicBpmnService.class, dynamicBpmnService);
});
}
@Override
public void stop() throws Throwable {
Plugin.super.stop();
}
}
表达管理器 (ProcessExpressionManager)
重要,如果没有添加自己的表达式管理,一些流程在执行的时候可能出错。
/**
* @author airhead
*/
public class SolonExpressionManager extends ProcessExpressionManager {
protected AppContext appContext;
public SolonExpressionManager(AppContext appContext, Map<Object, Object> beans) {
super(beans);
this.appContext = appContext;
}
@Override
protected ELResolver createElResolver(VariableContainer variableContainer) {
List<ELResolver> elResolvers = new ArrayList<>();
elResolvers.add(createVariableElResolver(variableContainer));
elResolvers.add(new AppContextElResolver(this.appContext));
if (beans != null) {
elResolvers.add(new ReadOnlyMapELResolver(beans));
}
elResolvers.add(new ArrayELResolver());
elResolvers.add(new ListELResolver());
elResolvers.add(new MapELResolver());
elResolvers.add(new JsonNodeELResolver());
ELResolver beanElResolver = createBeanElResolver();
if (beanElResolver != null) {
elResolvers.add(beanElResolver);
}
configureResolvers(elResolvers);
CompositeELResolver compositeElResolver = new CompositeELResolver();
for (ELResolver elResolver : elResolvers) {
compositeElResolver.add(elResolver);
}
compositeElResolver.add(new CouldNotResolvePropertyELResolver());
return compositeElResolver;
}
}
ELResolver
EL 表达式执行器,用于处理 Solon bean 对象的获取。
/**
* @author airhead
*/
@Slf4j
public class AppContextElResolver extends ELResolver {
protected AppContext appContext;
public AppContextElResolver(AppContext appContext) {
this.appContext = appContext;
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object arg) {
return Object.class;
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object arg) {
return null;
}
@Override
public Class<?> getType(ELContext context, Object arg1, Object arg2) {
return Object.class;
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
String key = property.toString();
Object bean = appContext.getBean(key);
if (bean != null) {
context.setPropertyResolved(true);
return bean;
}
}
return null;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null) {
String key = (String) property;
this.appContext.getBeanAsync(
key,
(bean) -> {
throw new FlowableException(
"Cannot set value of '"
+ property
+ "', it resolves to a bean defined in the Solon application-context.");
});
}
}
}
问题
- 提示模型注册失败,并提示
java.time.LocalDateTime cannot be cast to java.lang.String
主要的问题,集成的是 6.5.0的 Flowable,不支持高版本的 MySQL 驱动。检查发现Spring 版本使用的是 8.0.20的MySQL 驱动,而 Solon 使用的是 8.0.30 版本的驱动。把MySQL 驱动版本恢复为8.0.20,服务启动成功。
- 提示找不到对象 TaskListener 相关的表达式对象,一些流程条件的时候提示如下的错误。
原因是虽然通过 Component 注册了 Bean 对象,但 Flowable 无法获取,需有重写 ExpressionManager,增加Solon bean 对象的获取。
这里需要注意,注册 ExpressionManager 时,需要在
ProcessEngine processEngine = engineConfiguration.buildProcessEngine()
之前否则可能构建了两个ExpressionManager,在执行获取 TaskListener 的对象时仍然可能报错。// 设置表达式管理器 ExpressionManager ficusExpressionManager = new SolonExpressionManager(appContext, null); engineConfiguration.setExpressionManager(ficusExpressionManager);
增加 SolonExpressionManager,继承自 ProcessExpressionManager。重写 EL 表达式执行器,多增加AppContextElResolver。
@Override protected ELResolver createElResolver(VariableContainer variableContainer) { List<ELResolver> elResolvers = new ArrayList<>(); elResolvers.add(createVariableElResolver(variableContainer)); elResolvers.add(new AppContextElResolver(this.appContext)); if (beans != null) { elResolvers.add(new ReadOnlyMapELResolver(beans)); } elResolvers.add(new ArrayELResolver()); elResolvers.add(new ListELResolver()); elResolvers.add(new MapELResolver()); elResolvers.add(new JsonNodeELResolver()); ELResolver beanElResolver = createBeanElResolver(); if (beanElResolver != null) { elResolvers.add(beanElResolver); } configureResolvers(elResolvers); CompositeELResolver compositeElResolver = new CompositeELResolver(); for (ELResolver elResolver : elResolvers) { compositeElResolver.add(elResolver); } compositeElResolver.add(new CouldNotResolvePropertyELResolver()); return compositeElResolver; }
增加 AppContextElResolver,继承自 ELResolver,获取 Bean 对象的代码,这里需要注意如果对象创建了,需要设置
context.setPropertyResolved(true)
。@Override public Object getValue(ELContext context, Object base, Object property) { if (base == null) { String key = property.toString(); Object bean = appContext.getBean(key); if (bean != null) { context.setPropertyResolved(true); return bean; } } return null; }