
Flowable CDI 集成指南用 Java EE / CDI 打通 BPM 流程与业务上下文【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine导读本文基于 Flowable 开源仓库中flowable-cdi模块源码位于 modules/flowable-cdi讲解 Flowable 与 CDIContexts and Dependency InjectionJava EE 上下文与依赖注入的深度集成方案。flowable-cdi 是一个同时作用于 Flowable 可配置性与 CDI 可扩展性的桥梁模块它让开发者可以用 CDI 原生的方式管理业务流程将 Bean 的生命周期绑定到流程实例BusinessProcessScoped、在 BPMN 表达式中直接引用 CDI Bean 与 EJB、用注解声明式地启动流程与完成任务并把引擎事件投递到 CDI 事件总线。读完本文你将掌握 flowable-cdi 的环境搭建、上下文关联模型、注解式流程控制、变量注入以及事件监听等完整实战能力。一、flowable-cdi 的核心能力概览flowable-cdi 模块把 Flowable 的可配置性与 CDI 的可扩展性结合起来其最突出的特性包括对应 ch15-Cdi.md 原文的 feature 清单支持BusinessProcessScopedBeanCDI Bean 的生命周期被绑定到某个流程实例自定义 EL-Resolver允许在流程中解析 CDI Bean包括 EJB声明式流程控制通过注解控制流程实例的启动与任务的完成接入 CDI 事件总线Flowable 引擎事件可以通过标准 CDI 事件机制被感知多环境适配同时适用于 Java EE 与 Java SE也支持与 Spring 协作单元测试支持为 CDI 环境下的流程逻辑提供测试基础。引入该模块只需在 Maven 依赖中声明坐标以仓库 modules/flowable-cdi/pom.xml 为准dependency groupIdorg.flowable/groupId artifactIdflowable-cdi/artifactId version7.x/version /dependency从源码结构看该模块的核心类集中在 modules/flowable-cdi/src/main/java/org/flowable/cdi 下包括BusinessProcess上下文关联控制 Bean、CdiExpressionManagerEL 表达式管理、CdiJtaProcessEngineConfiguration/CdiStandaloneProcessEngineConfiguration两类引擎配置、ProcessVariablesEL 变量访问入口以及annotation、impl、spi三个子包中的注解、拦截器与 SPI 实现。二、环境搭建让 CDI 找到 ProcessEngine2.1 ProcessEngineLookup SPI引擎查找策略CDI 扩展FlowableExtension位于 modules/flowable-cdi/src/main/java/org/flowable/cdi/impl/FlowableExtension.java在启动时需要拿到一个ProcessEngine。为此flowable-cdi 定义了 SPI 接口 org.flowable.cdi.spi.ProcessEngineLookup其契约包括getPrecedence()返回优先级数值越高越先被调用getProcessEngine()在扩展启动时被调用一次返回引擎实例ungetProcessEngine()在扩展关闭时被调用一次用于释放引擎。模块内置了默认实现 LocalProcessEngineLookup它借助ProcessEngines工具类按名称查找引擎默认使用ProcessEngines.NAME_DEFAULT。该实现同样可以通过继承子类并调用setProcessEngineName(...)来指定自定义引擎名。源码注释明确说明使用该策略的前提是 classpath 上存在flowable.cfg.xml见LocalProcessEngineLookup类的 Javadoc。接口解析借助 Java 的java.util.ServiceLoader机制完成。若要提供自定义实现需要在部署包中放置一个纯文本文件META-INF/services/org.flowable.cdi.spi.ProcessEngineLookup文件内容为自定义实现类的全限定类名。注意如果不提供自定义的ProcessEngineLookup实现Flowable 将使用默认的LocalProcessEngineLookup。此时你只需要在 classpath 上提供flowable.cfg.xml见下一节即可。2.2 通过 flowable.cfg.xml 配置引擎配置方式取决于所选用的查找策略。与LocalProcessEngineLookup搭配时需要在 classpath 上提供一份基于 Spring Bean 定义的flowable.cfg.xml。Flowable 本身提供多种ProcessEngineConfiguration实现主要区别在于底层事务管理策略flowable-cdi 模块不关心事务因此原则上可以使用任何一种事务管理策略甚至包括 Spring 事务抽象。为方便使用CDI 模块额外提供了两个专用配置类org.flowable.cdi.CdiJtaProcessEngineConfiguration继承自 Flowable 的JtaProcessEngineConfiguration适用于希望由 JTA 管理 Flowable 事务的场景org.flowable.cdi.CdiStandaloneProcessEngineConfiguration继承自StandaloneProcessEngineConfiguration适用于使用纯 JDBC 事务的场景。两个配置类的构造函数都会做两件关键事情见源码通过addPreDefaultELResolver(new CdiResolver())注册 CDI 的 EL 解析器通过addAgendaOperationExecutionListener(new CdiAgendaOperationExecutionListener())注册议程执行监听器并在initBehaviorFactory()中默认装配DefaultCdiActivityBehaviorFactory。这意味着只要使用这两个配置类CDI 表达式解析能力是默认开启的。以下是 JBoss 7 下的示例flowable.cfg.xmlJTA JNDI 数据源?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd !-- lookup the JTA-Transaction manager -- bean idtransactionManager classorg.springframework.jndi.JndiObjectFactoryBean property namejndiName valuejava:jboss/TransactionManager/property property nameresourceRef valuetrue / /bean !-- process engine configuration -- bean idprocessEngineConfiguration classorg.flowable.cdi.CdiJtaProcessEngineConfiguration !-- lookup the default Jboss datasource -- property namedataSourceJndiName valuejava:jboss/datasources/ExampleDS / property namedatabaseType valueh2 / property nametransactionManager reftransactionManager / !-- using externally managed transactions -- property nametransactionsExternallyManaged valuetrue / property namedatabaseSchemaUpdate valuetrue / /bean /beans下面是 Glassfish 3.1.1 下的配置假设已正确配置名为jdbc/flowable的数据源?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd !-- lookup the JTA-Transaction manager -- bean idtransactionManager classorg.springframework.jndi.JndiObjectFactoryBean property namejndiName valuejava:appserver/TransactionManager/property property nameresourceRef valuetrue / /bean !-- process engine configuration -- bean idprocessEngineConfiguration classorg.flowable.cdi.CdiJtaProcessEngineConfiguration property namedataSourceJndiName valuejdbc/flowable / property nametransactionManager reftransactionManager / !-- using externally managed transactions -- property nametransactionsExternallyManaged valuetrue / property namedatabaseSchemaUpdate valuetrue / /bean /beans配置要点说明transactionManager通过 JNDI 查找应用服务器的 JTA 事务管理器resourceReftrue表示使用组件环境的 JNDI 命名java:comp/env前缀来解析dataSourceJndiName指定应用服务器上数据源的 JNDI 名称transactionsExternallyManagedtrue表示事务由外部JTA 容器管理Flowable 不自行提交databaseSchemaUpdatetrue表示启动时自动创建/更新数据库 Schema生产环境建议显式管理 Schema 后关闭。注意上述配置依赖spring-context模块flowable.cfg.xml由 Spring 容器解析dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version4.2.5.RELEASE/version /dependencyJava SE 环境的配置方式与创建 ProcessEngine 一节中给出的示例完全一致只需把StandaloneProcessEngineConfiguration替换为CdiStandaloneProcessEngineConfiguration即可。2.3 进程部署RepositoryService 与 processes.xml 自动部署进程既可以使用标准 Flowable APIRepositoryService部署也可以利用 flowable-cdi 提供的自动部署能力把待部署的进程列表写在一个位于 classpath 根目录的processes.xml文件中扩展启动时会自动解析并部署。示例?xml version1.0 encodingutf-8 ? !-- list the processes to be deployed -- processes process resourcediagrams/myProcess.bpmn20.xml / process resourcediagrams/myOtherProcess.bpmn20.xml / /processes底层实现见 ProcessDeployerdeployProcesses()会通过 classloader 查找名为processes.xml的资源用 DOM 解析出所有process resource.../的resource属性随后通过RepositoryService.createDeployment()将全部资源作为一个 Deployment 批量部署若资源集合为空则不会创建部署。deployProcess(String)则负责单进程部署并通过createProcessDefinitionQuery().deploymentId(...)返回部署后得到的processDefinitionId。三、上下文流程执行模型Contextual Process ExecutionBPMN 业务流程通常是长时间运行的交互由用户任务和系统任务共同组成运行时流程被拆解为一组由用户和/或应用逻辑执行的工作单元。在 flowable-cdi 中流程实例可以关联到一个 CDI 作用域该关联即代表一个工作单元。当一个工作单元足够复杂时——比如一个用户任务的实现是一串不同表单的复杂序列并且这个交互过程中需要保留非流程作用域的状态——这种关联模型尤其有用。在默认配置下流程实例会关联到当前最宽的活动作用域优先 Conversation会话如果 Conversation 上下文不活跃则回退到 Request请求。3.1 BusinessProcess Bean关联的控制中枢在解析BusinessProcessScopedBean 或注入流程变量时都依赖一个活跃的 CDI 作用域与一个流程实例之间已建立的关联。flowable-cdi 提供了 BusinessProcess 这个 Bean 来控制关联其中最常用的方法包括startProcessBy*(…)系列方法镜像RuntimeService暴露的对应方法用于启动业务流程并随后建立关联。源码中可见startProcessById、startProcessByKey、startProcessByMessage及其带businessKey/variables的重载启动成功后若流程实例未结束则调用setExecution(instance)建立关联resumeProcessById(String processInstanceId)将给定 id 的流程实例关联到当前上下文resumeTaskById(String taskId)将给定 id 的任务以及与之对应的流程实例关联到当前上下文。startTask(String)的实现会先校验任务存在再调用associateExecutionById(task.getProcessInstanceId())完成任务 执行的双重关联。一旦一个工作单元例如某个用户任务完成就可以调用completeTask()方法解除会话/请求与流程实例的关联。这会通知引擎当前任务已完成并使流程实例继续推进。completeTask(boolean endConversation)重载在完成任务后还可选择性地结束当前会话。BusinessProcess是一个NamedBean见源码类级注解这意味着它暴露的方法可以通过表达式语言调用例如从 JSF 页面。下面的 JSF2 片段开启一个新会话并将其关联到一个用户任务实例任务 id 作为请求参数传入例如pageName.jsf?taskIdXXf:metadata f:viewParam nametaskId / f:event typepreRenderView listener#{businessProcess.startTask(taskId, true)} / /f:metadata从BusinessProcess的 Javadoc 可以看到一套典型的工作单元协议源码注释中的使用场景第一个工作单元流程实例化 conversation.begin(); businessProcess.setVariable(billingId, 1); // 启动流程前设置变量 businessProcess.startProcessByKey(billingProcess); conversation.end(); 第二个工作单元执行用户任务 conversation.begin(); businessProcess.startTask(id); // 将任务关联到当前会话 businessProcess.setVariable(billingDetails, someValue); // 变更被缓存在会话中 businessProcess.completeTask(); // 此时所有变更的流程变量被刷新到引擎 conversation.end();另外需要留意两个上下文回退细节同样来自 Javadoc在缺少 Conversation 的非 Faces 请求中如 JAX-RS、JAX-WS、JMS、远程 EJB 或普通 Servlet 请求BusinessProcess会关联到当前的 Request在缺少 Request 的场景例如 JobExecutor 访问BusinessProcessScopedBean下执行会关联到当前线程。3.2 声明式地控制流程flowable-cdi 允许用注解声明式地启动流程实例和完成任务。StartProcess注解定义既可以按key也可以按name启动流程实例。注意流程实例是在被注解方法返回之后才启动的。示例StartProcess(authorizeBusinessTripRequest) public String submitRequest(BusinessTripRequest request) { // do some work return success; }其底层拦截器 StartProcessInterceptor 在AroundInvoke中先执行ctx.proceed()再读取注解上的name与value若指定了name则调用startProcessByName否则调用startProcessByKey。拦截器还会扫描声明类上带有ProcessVariable注解的字段将其值作为流程变量一并传递给启动方法——这是启动前设置变量的声明式等价物。CompleteTask注解定义的工作方式类似同样在方法返回后生效CompleteTask(endConversationfalse) public String authorizeBusinessTrip() { // do some work return success; }CompleteTask注解提供了结束当前会话的选项默认行为是在 Flowable 调用返回后结束会话如上面的示例所示可以通过endConversationfalse禁用这一行为。拦截器 CompleteTaskInterceptor 在方法成功返回后调用businessProcess.completeTask(endConversation)若被注解方法抛出异常则任务不会被完成对应CompleteTask的 Javadoc 说明。事务边界根据 Flowable 的配置被注解方法的代码与流程实例的启动/任务完成会被组合进同一个事务例如使用 JTA 配置时由容器统一管理事务边界。3.3 从流程中引用 Beanflowable-cdi 通过自定义解析器CdiResolver把 CDI Bean 暴露给 Flowable 的 EL。CdiResolver内部持有从BeanManager获取的jakarta.el.ELResolver在getValue时先尝试委托给该 resolver若抛出IllegalStateException典型于 Dependent 作用域或 EJB 场景则回退到ProgrammaticBeanLookup.lookup(...)做程序化查找。由于它执行 BeanManager 的懒加载查找因此也可以作为普通的customElResolvers配置项手工注册不过一旦使用CdiStandaloneProcessEngineConfiguration或CdiJtaProcessEngineConfiguration该 resolver 已被默认注册无需再配置见CdiResolver类 Javadoc。这让流程可以引用 Bean例如userTask idauthorizeBusinessTrip nameAuthorize Business Trip flowable:assignee#{authorizingManager.account.username} /其中authorizingManager可以是一个由 producer 方法提供的 BeanInject ProcessVariable Object businessTripRequesterUsername; Produces Named public Employee authorizingManager() { TypedQueryEmployee query entityManager.createQuery(SELECT e FROM Employee e WHERE e.account.username businessTripRequesterUsername , Employee.class); Employee employee query.getSingleResult(); return employee.getManager(); }同样的能力可用于在服务任务Service Task中调用某个 EJB 的业务方法使用flowable:expressionmyEjb.method()扩展属性。注意这要求MyEjb类上带有Named注解。3.4 使用 BusinessProcessScoped Bean使用 flowable-cdi 可以把某个 Bean 的生命周期绑定到流程实例上。为此模块提供了一个自定义上下文实现 BusinessProcessContext位于impl/context包。BusinessProcessScopedBean 的实例会作为流程变量存储在当前流程实例中因此它必须是可序列化的实现Serializable即具备 PassivationCapable 能力。示例Named BusinessProcessScoped public class BusinessTripRequest implements Serializable { private static final long serialVersionUID 1L; private String startDate; private String endDate; // ... }从BusinessProcessContext源码可以看到其存取逻辑get(contextual)会以 Bean 的Named名称作为变量名调用businessProcess.getVariable(variableName)从流程变量中取值get(contextual, creationalContext)在变量不存在时调用bean.create(arg1)创建实例并setVariable回流程变量从而实现按需创建、绑定流程的语义。其isActive()恒返回true意味着该上下文始终活跃。无关联时的临时存储有时我们希望在尚不存在流程实例关联的情况下使用流程作用域 Bean——例如启动流程之前。此时若没有活跃的流程实例BusinessProcessScopedBean 的实例会临时存储在本地作用域中根据上下文情况为 Conversation 或 Request当该作用域稍后与某个业务流程实例建立关联时这些 Bean 实例会被冲刷flush到流程实例中。3.5 注入流程变量流程变量支持注入。flowable-cdi 支持两种方式类型安全的注入使用Inject [附加限定符] Type 字段名注入BusinessProcessScopedBean非安全的注入使用ProcessVariable(name?)限定符注入其他流程变量Inject ProcessVariable Object accountNumber; Inject ProcessVariable(accountNumber) Object accountProcessVariable注解定义位于 annotation/ProcessVariable.java其可选value用于指定变量名缺省时以字段名作为变量名。在 EL 中引用流程变量也有对应选项Named BusinessProcessScopedBean 可以直接引用其他流程变量可以通过ProcessVariablesBean 引用#{processVariables[accountNumber]}ProcessVariables类位于 modules/flowable-cdi/src/main/java/org/flowable/cdi/ProcessVariables.java。3.6 接收流程事件CDI 事件总线Flowable 可以接入 CDI 事件总线从而用标准 CDI 事件机制感知流程事件。要开启 CDI 事件支持需在配置中启用对应的解析监听器parse listenerproperty namepostBpmnParseHandlers list bean classorg.flowable.cdi.impl.event.CdiEventSupportBpmnParseHandler / /list /property实现对 CdiEventSupportBpmnParseHandler 的行为可以精确印证事件机制的底层实现它声明支持从 StartEvent、EndEvent、各类 Gateway、ServiceTask、UserTask、SubProcess、CallActivity、Transaction、ThrowEvent 到 SequenceFlow、Timer/Error/Signal 事件定义等一整套 BPMN 元素见其静态初始化块解析时为SequenceFlow注册ExecutionListener.EVENTNAME_TAKE监听器CdiExecutionListener为UserTask注册 create / assign / complete / delete 四类TaskListenerCdiTaskListener分别映射BusinessProcessEventType.CREATE_TASK、ASSIGN_TASK、COMPLETE_TASK、DELETE_TASK为其余FlowElement注册 start / end 两类执行监听器映射START_ACTIVITY、END_ACTIVITY。这些监听器把引擎事件翻译成 CDI 事件通过BusinessProcessEvent发布。开启后即可在 CDI Bean 中声明式地观察事件。CDI 中用Observes注解声明事件观察者事件通知是类型安全的流程事件的类型为 org.flowable.cdi.BusinessProcessEvent。一个最简单的观察者方法public void onProcessEvent(Observes BusinessProcessEvent businessProcessEvent) { // handle event }这个观察者会收到所有事件。如果想限定观察者接收的事件集合可以附加限定符注解定义位于 annotation/event 包BusinessProcess将事件集合限定到某个流程定义。例如Observes BusinessProcess(billingProcess) BusinessProcessEvent evtStartActivity按某个活动限定。例如Observes StartActivity(shipGoods) BusinessProcessEvent evt在 id 为shipGoods的活动被进入时触发EndActivity按某个活动限定。例如Observes EndActivity(shipGoods) BusinessProcessEvent evt在 id 为shipGoods的活动被离开时触发TakeTransition按某条流转transition限定CreateTask按某个任务的创建限定DeleteTask按某个任务的删除限定AssignTask按某个任务的分配限定CompleteTask按某个任务的完成限定。上述限定符可以自由组合。例如要接收shipmentProcess流程中离开shipGoods活动时产生的所有事件public void beforeShippingGoods(Observes BusinessProcess(shippingProcess) EndActivity(shipGoods) BusinessProcessEvent evt) { // handle event }事务性观察者在默认配置下事件监听器以同步方式、在同一事务上下文中被调用。CDI 事务性观察者仅在使用 JavaEE/EJB 时可用允许控制事件被交给观察者的时机。例如可以保证只有事件触发所在的事务成功提交后观察者才会被通知public void onShipmentSucceeded(Observes(duringTransactionPhase.AFTER_SUCCESS) BusinessProcess(shippingProcess) EndActivity(shipGoods) BusinessProcessEvent evt) { // send email to customer. }3.7 附加功能除了以上能力flowable-cdi 还提供了一组便捷的注入点ProcessEngine及各服务RepositoryService、TaskService等都可以注入Inject ProcessEngine, RepositoryService, TaskService, …当前流程实例和任务可以注入Inject ProcessInstance, Task当前业务键business key可以注入Inject BusinessKey String businessKeyBusinessKey定义于 annotation/BusinessKey.java当前流程实例 id 可以注入Inject ProcessInstanceId String pidProcessInstanceId定义于 annotation/ProcessInstanceId.java。这些注入点的生产者集中在impl/annotation包如 BusinessKeyProducer以及CurrentProcessInstance等类中感兴趣的读者可以直接阅读源码印证。四、单元测试支持flowable-cdi 面向单元测试也做了配套设计。测试目录 modules/flowable-cdi/src/test/java/org/flowable/cdi 下提供了基类 CdiFlowableTestCase 与自定义扩展 FlowableCdiExtension用于在测试中装配 CDI 容器与引擎针对注解能力的测试如api/annotation下的 StartProcessTest.java、CompleteTaskTest.java、BusinessKeyTest.java、ProcessIdTest.java、TaskIdTest.java上下文与 EL 相关测试impl/context下的 BusinessProcessContextTest.java 与 ThreadContextAssociationTest.javaimpl/el下的 ElTest.java事件机制测试impl/event下的 EventNotificationTest.java、MultiInstanceServiceTaskEventTest.java、MultiInstanceTaskCompleteEventTest.java以及观察者 TestEventListener.java。这些测试同时为上述各特性的实际调用方式提供了最直接的可运行范例是深入理解 flowable-cdi 行为的上佳参考。五、已知限制虽然 flowable-cdi 是针对 SPI 实现、并按可移植扩展portable extension设计但它目前只使用 Weld 做过测试。这意味着对其它 CDI 实现如 OpenWebBeans、Apache Bean Validation 之外的其他容器实现的兼容性没有被系统验证在生产环境选用 CDI 实现时建议优先使用 Weld或基于 Weld 的应用服务器并针对目标容器做充分的回归验证。结语flowable-cdi 把 Flowable 的流程能力与 CDI 的上下文模型做了深度缝合从ProcessEngineLookup的 SPI 扩展点、CdiJtaProcessEngineConfiguration/CdiStandaloneProcessEngineConfiguration的引擎装配到BusinessProcess的上下文关联协议、StartProcess/CompleteTask的声明式控制、CdiResolver的 EL 解析、BusinessProcessContext的流程作用域 Bean再到CdiEventSupportBpmnParseHandler驱动的事件总线整个链路都围绕流程实例 CDI 工作单元这一核心模型展开。对于构建在 Java EE 容器之上的企业级 BPM 应用这套集成方案能让业务代码以 CDI 的惯用方式与流程引擎协作值得在 modules/flowable-cdi 源码与测试的基础上继续深挖。【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考