说明 最近在做的事情就是把 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 插件的方式封装一部分初始化工作。
配置 1 2 3 4 5 6 7 8 9 10 11 @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 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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) 重要,如果没有添加自己的表达式管理,一些流程在执行的时候可能出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 对象的获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @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 的对象时仍然可能报错。
1 2 3 ExpressionManager ficusExpressionManager = new SolonExpressionManager(appContext, null ); engineConfiguration.setExpressionManager(ficusExpressionManager);
增加 SolonExpressionManager,继承自 ProcessExpressionManager。重写 EL 表达式执行器,多增加AppContextElResolver。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @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)。
1 2 3 4 5 6 7 8 9 10 11 12 @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 ; }