Solon 集成 Flowable

说明

最近在做的事情就是把 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.");
          });
    }
  }
}

问题

  1. 提示模型注册失败,并提示 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,服务启动成功。

  1. 提示找不到对象 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;
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值