Java精选面试题 (微信小程序): 5000+ 道面试题和选择题, 真实面经 , 简历模版 ,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计、大厂真题等,在线随时刷题!
一、工作流介绍

较早的⼯作流是jBPM,这是⼀个由Java实现的企业级流程引擎,是JBoss公司开发的产品之⼀。jBPM 的创建者是Tom Baeyens,这个⼤佬后来离开了JBoss,并加⼊到Alfresco,并推出了基于jBPM4的开源⼯作流系统Activiti,⽽jBPM则在后续的代码中完全放弃了jBPM4的代码。

从这个过程中也能看出来,jBPM 在发展过程中,由于意⻅相左,后来变成了两个jBPM和Activiti。然⽽戏剧的是,Activiti5没搞多久,从Activiti中⼜分出来⼀个Camunda,Activiti继续发展,⼜从中分出来⼀个Flowable。

现在市⾯上主流的流程引擎就⼀共有三个:Activiti、Flowable、Camunda这三个各有特点:

1.Activiti ⽬前是侧重云,他⽬前的设计会向Spring Cloud、Docker 这些去靠拢 。

2.Flowable 核⼼思想还是在做⼀个功能丰富的流程引擎⼯具,除了最最基础的⼯作流,他还提供了很多其他的扩展点,我们可以基于 Flowable 实现出许多我们想要的功能。

3.Camunda 相对于前两个⽽⾔⽐较轻量级,Camunda 有⼀个⽐较有特⾊的功能就是他提供了⼀个⼩巧的编辑器,基于 bpmn.io 来实现的。如果 你的项⽬需求是做⼀个轻巧的、灵活的、定制性强的编辑器,⼯作流是嵌⼊式的,那么可以选择 Camunda 。

二、相关概念

以简单的请假流程为例:

打开网易新闻 查看精彩图片

一个流程图主要包含四个方面:

  • 事件

  • 连线

  • 任务

  • 网关

事件

在一个流程图中肯定要有的是开始事件和结束事件,就是上图中的圆圈,此外还有一些中间事件,边界事件等。

连线

连线是将事件,任务,网关等连在一起的线条,一般情况下都是普通连线,有的会有一些条件,比如上图中从网关出来的连线需要根据请假申请的审批结果确定走那一条连线

任务

  • 接收任务

打开网易新闻 查看精彩图片

这个任务并不需要处理额外的事,流程到这一步就自动停下来了,需要人工点一下,推动流程继续向下执行。

  • 发送任务

打开网易新闻 查看精彩图片

一般用来吧消息发送给外部参与者

  • 服务任务

打开网易新闻 查看精彩图片

一般由系统自动完成,我们需要自定义一个类,可以在这个自定义的类里面完成自己想要做的事,比较灵活

  • 脚本任务

打开网易新闻 查看精彩图片

一个自动化活动,当流程执行到脚本任务时,自动执行相应的脚本

  • 用户任务

打开网易新闻 查看精彩图片

用于为那些需要人工参与者完成的工作建模

服务主要分为两大类

  • 用户任务:一般表示人工需要介入的事情,比如是否同意,或者一些表单输入,需要让人工完成,一般用户任务和表单结合在一起,用户任务需要用户向引擎提交一个完成任务的动作,否则流程会暂停在这里等待。

  • 服务任务:服务任务会自动执行,调用服务任务的时候,可以是一个javabean,也可以是一个类的全限定名,也可以是一个远程rest服务,流程会自动执行服务任务。

网关
  • 互斥网关

打开网易新闻 查看精彩图片

也叫排他性网关,这种网关只有一个有效出口

  • 相容网关

打开网易新闻 查看精彩图片

这种网关有多个出口,只要满足条件,都会执行

  • 事件网关

打开网易新闻 查看精彩图片

事件网关是通过中间事件驱动,它在等待的事件发生后才会触发决策,基于事件的网关允许基于事件做出决策

  • 并行网关

并行网关一般是成对出现的,如果有一些任务可以并行执行,那么可以用并行网关。

三、集成Flowable 3.1 创建Spring Boot 项目并引入依赖

idea 创建spring boot 项目相信大家都很熟练了,就不再赘述了,因为flowable需要用到数据库,所以需要引入mysql和mybatis依赖

引入flowable依赖


     

 org.flowable groupId>     

 flowable-spring-boot-starter artifactId>     

 6.7.2 version> dependency>



默认情况下,位于resources/processes目录下的历程都会被自动部署

配置数据库连接信息,项目启动会自动初始化数据库(建表,初始化流程信息),流程引擎运行时候的数据也会被保存到数据库中。

spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://ip:port/process?useSSL=false&useUnicode=true&characterEncoding=utf-8
    username: username
    password: pw
3.2 画流程图

可以集成flowableUI画,也可以使用idea插件

本文使用Flowable BPMN visualizer插件画流程图

打开网易新闻 查看精彩图片

在processes目录下新建流程文件

打开网易新闻 查看精彩图片

本文新建的为ask_for_leave.bpmn20.xml.bpmn20.xml是固定后缀

在文件上右击,选择View BPMN(Flowable) Diagram就可以可视化的绘制流程图了

如果需要新添加一个流程图元素,则需要右键,根据需要选择即可

打开网易新闻 查看精彩图片

我画的流程图如下:

打开网易新闻 查看精彩图片

1、首先根据业务流程把各个流程画出并链接起来,开始事件–>员工的用户任务(员工发起请假流程)–>组长用户任务(组长审批是否同意)–>组长审批网关(如果不同意,则发送失败提示并结束流程,如果同意进入经理审批任务)–> 经理审批任务(经理审批是否同意)–> 组长审批网关(如果不同意,则发送失败提示并结束流程,如果同意结束流程)

2、员工用户任务配置:需要注意的点在于需要配置Assignee(办理人)属性,该属性一般为员工id

打开网易新闻 查看精彩图片

3、组长审批 用户任务 和 经理审批用户任务的配置和员工的类似,办理人都需要指定为用户ID

4、组长审批网关和经理审批网关我们使用互斥网关,因为在审批的时候只能是同意或者不同意,其他没有特殊配置

打开网易新闻 查看精彩图片

5、组长审批通过连线:需要配置条件表达式,用来判断什么情况下走这个连线,我们配置表达式为${var:equals(checkResult,“通过”)},这个地方的表达式是el表达式,会自动计算,checkResult是在组长审批的用户任务中,表单提交的数据

打开网易新闻 查看精彩图片

6、发送失败提示 服务任务:服务任务会自动执行,所需要指定class 这个类需要自己实现接口

打开网易新闻 查看精彩图片

自动生成的流程定义文件


   

     


       

 员工请假 documentation>      userTask>     






       

 ${var:equals(checkResult,"通过")} conditionExpression>      sequenceFlow>     

       

 ${var:equals(checkResult,"拒绝")} conditionExpression>      sequenceFlow>     







       

 ${var:equals(checkResult,"拒绝")} conditionExpression>      sequenceFlow>     


       

 ${var:equals(checkResult,"通过")} conditionExpression>      sequenceFlow>    process>   

     

       

         
bpmndi:BPMNShape>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         


bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         


bpmndi:BPMNEdge>       

         
bpmndi:BPMNShape>       

         

bpmndi:BPMNEdge>       

         

bpmndi:BPMNEdge>      bpmndi:BPMNPlane>    bpmndi:BPMNDiagram> definitions>































3.3 服务任务实现类

@Slf4j
@Component
public class LeaveFailService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) {
        log.info("审批不通过{}", delegateExecution.getCurrentActivityId());
    }
}

需要实现 JavaDelegate接口;在执行到服务任务的时候,就会自动调用这个方法,在这个方法中我们可以做一些自定义的业务逻辑

3.4 流程图查看接口

为了方便查看流程执行到那一步了,故增加该接口查看

开启流程的时候会生成一个流程id,使用该流程ID可查询流程进行到那一步了

/**  * LeaveController  *  * @Description:  * @Author: wangyubiao  * 标签属性   * 网关:  * @Date 2023/5/12 13:26  * @since 1.0.0  */
@RestController
public class LeaveController {

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    ProcessEngine processEngine;

    @GetMapping("/pic")
    public void showPic(HttpServletResponse resp, String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
        if (pi == null) {
            return;
        }
        List
     
  executions = runtimeService                 .createExecutionQuery()                 .processInstanceId(processId)                 .list();         List  activityIds =  new ArrayList<>();         List  flows =  new ArrayList<>();          for (Execution exe : executions) {             List  ids = runtimeService.getActiveActivityIds(exe.getId());             activityIds.addAll(ids);         }          /**          * 生成流程图          */         BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());         ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();         ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();         InputStream in = diagramGenerator.generateDiagram(bpmnModel,  "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(),  1.0,  false);         OutputStream out =  null;          byte[] buf =  new  byte[ 1024];          int legth =  0;          try {             out = resp.getOutputStream();              while ((legth = in.read(buf)) != - 1) {                 out.write(buf,  0, legth);             }         }  finally {              if (in !=  null) {                 in.close();             }              if (out !=  null) {                 out.close();             }         }     } }

查询结果:

3.5 单元测试
打开网易新闻 查看精彩图片
3.5 单元测试
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootTest
@Slf4j
@ActiveProfiles("dev")
class ProcessApplicationTests {

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    TaskService taskService;

     // 员工id 
    public static final String yuangongId = "yuangongID_3";


     // 员工id 
    public static final String zuzhangId = "zuzhangId_3";

     // 员工id 
    public static final String manageId = "manageId_3";

    @Test
    void contextLoads() {
    }


    /**      * 开启一个请假流程      */
    @Test
    void askForLeave() {
        HashMap
     
  map =  new HashMap<>();         map.put( "leaveTask", yuangongId);          // 开启流程的key,就是流程定义文件里 process 标签的id         ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "ask_for_leave", map);          // 设置一些参数         runtimeService.setVariable(processInstance.getId(),  "name",  "javaboy");         runtimeService.setVariable(processInstance.getId(),  "reason",  "休息一下");         runtimeService.setVariable(processInstance.getId(),  "days",  10);         log.info( "创建请假流程 processId:{}", processInstance.getId());     }      /**      * 员工提交请假      */      @Test      void submitToZuZhang() {          // 员工查找到自己的任务,然后提交给组长审批         List  list = taskService.createTaskQuery().taskAssignee(yuangongId).orderByTaskId().desc().list();          for (Task task: list) {             log.info( "任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());             Map  map =  new HashMap<>();              //提交给组长的时候,需要指定组长的 id             map.put( "zuzhangTask", zuzhangId);             taskService.complete(task.getId(), map);         }     }      /**      * 组长批准请假      */      @Test      void zuZhangApprove() {         List  list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();          for (Task task: list) {              if ( "组长审批".equals(task.getName())) {                 log.info( "任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());                 Map  map =  new HashMap<>();                  //提交给组长的时候,需要指定组长的 id                 map.put( "manageTask", manageId);                 map.put( "checkResult",  "通过");                 map.put( "zuzhangTask", zuzhangId);                  try {                     taskService.complete(task.getId(), map);                 }  catch (Exception e) {                     e.printStackTrace();                     log.error( "组长审批失败{} {}", task.getId(), task.getAssignee());                 }             }         }     }      /**      * 经理审批通过      */      @Test      void managerApprove() {         List  list = taskService.createTaskQuery().taskAssignee(manageId).orderByTaskId().desc().list();          for (Task task: list) {             log.info( "经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());             Map  map =  new HashMap<>();             map.put( "checkResult",  "通过");             taskService.complete(task.getId(), map);         }     }      /**      * 经理审批不通过      */      @Test      void managerNotApprove() {         List  list = taskService.createTaskQuery().taskAssignee(manageId).orderByTaskId().desc().list();          for (Task task: list) {             log.info( "经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());             Map  map =  new HashMap<>();             map.put( "checkResult",  "拒绝");             taskService.complete(task.getId(), map);         }     } }
四、遇到的问题

4.1 生成的流程图文字显示为”口口口“

这是因为本地没有默认的字体,安装字体或者修改配置解决

import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;

/**  * FlowableConfig  *  * @Description:  * @Author: wangyubiao  * @Date 2023/5/15 15:16  * @since 1.0.0  */
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer

  {     @Override     public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {         springProcessEngineConfiguration.setActivityFontName("宋体");         springProcessEngineConfiguration.setLabelFontName("宋体");         springProcessEngineConfiguration.setAnnotationFontName("宋体");     } }
4.2 一个流程已经开始了,修改了流程定义文件,已经开始的流程并没有更新

流程开始后,流程配置信息已经持久化了,修改流程定义文件,只会影响还没开始的流程,不会对已经开始的流程造成影响

作者:人生漫漫唯有奋斗 来源:https://blog.csdn.net/qq_41539807

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有技术或摸鱼交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!在线摸鱼:https://www.yoodb.com/

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!

特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注

文章有帮助的话,点在看,转发吧!