宅急送 项目第九天 JBPM 进阶

1. JBPM入门知识回顾

1.1. 开发环境搭建

什么是工作流? 解决实际中怎样的问题?
软件系统面对各种复杂的业务流程,如果针对每个流程采用分别编码方式(将流程逻辑写到代码中)完成,不便于进行业务流程变更和扩展 。

工作流框架,将业务流程管理起来(保存到数据表中),只需要针对通用流程办理去编写程序 流程定义管理、流程实例管理、任务办理、流程关联变量 ,(程序与业务无关后) 去修改或增加新的流程,不需要修改之前的代码。

什么是JBPM ?
JBPM 是一个工作流框架 ,是JBOSS的开源免费的框架 ,课程主要学习4.4版本,JBPM默认和Hibernate框架整合。

安装流程设计器?
JBPM业务流程设计器分为在线和线下设计器,MyEclipse的GPD插件安装(线下设计器), 使用线下设计器,设计业务流程后,产生 jpdl.xml 和 png 文件,将产生流程文件,制作zip 压缩包,通过上传文件的方式,将业务流程发布到软件系统中。

在项目导入JBPM的jar包和配置文件?

${JBPM_HOME}/jbpm.jar 核心jar包
${JBPM_HOME}/lib/*.jar (导入jar包,防止jar包冲突)

项目需要JBPM两个配置文件 jbpm.cfg.xml 、jbpm.hibernate.cfg.xml
开发JBPM

Configuration configuration = new Configuration();
configuration.buildProcessEngine(); // 创建流程引擎 ,建立JBPM18张数据表

1.2. 流程定义管理

任何使用工作流框架的系统,都需要将业务流程,交给工作流管理
通过设计器设计业务流程, 将业务流程部署到系统中

1.2.1. 流程定义发布
processEngine.getRepositoryService().createDeployment() 获得发布对象
deployment.addResourceFromClassPath(xxx.jpdl.xml) 
deployment.addResourceFromZipInputStream
(new ZipInputStream(new FileInputStream(zipFile)));
deployment.deploy();

流程定义发布: jbpm4_deployment、jbpm4_lob、jbpm4_deployprop

1.2.2. 流程定义查看

流程定义属性查询:

 processEngine.getRepositoryService().createProcessDefinitionQuery(); 

流程图查看 :

processEngine.getRepositoryService().getResourceAsStream(deploymentId, resourceName)
1.2.3. 流程定义删除
processEngine.getRepositoryService().deleteDeploymentCascade(deploymentId); 
// 这里 deploymentId 发布编号,是 jbpm4_deployment 表主键 DBID

1.3. 流程实例管理

运行一个流程定义,产生具体业务实例

1.3.1. 启动流程实例

根据id启动:

 processEngine.getExecutionService().startProcessInstanceById(pdId) ;
 // 这里pdId 流程定义编号 jbpm4_deployprop 表 pdid 

根据key启动:

processEngine.getExecutionService().startProcessInstanceByKey(pdKey);
// 这里pdkey 流程定义关键字 jbpm4_deployprop 表 pdkey 
// 如果存在很多相同key的流程,默认启动最高版本的流程定义 
1.3.2. 流程实例流转

向后一步:

 processEngine.getExecutionService().signalExecutionById(executionId);
// 这里executionId 流程实例编号 jbpm4_execution表 ID_ 

直接中止流程:

processEngine.getExecutionService().endProcessInstance(executionId,. ProcessInstance.STATE_END); 

流程实例影响数据表:

 jbpm4_execution、jbpm4_hist_procinst、jbpm4_task、jbpm4_hist_task、jbpm4_hist_actinst 

1.4. 任务管理

在流程流转时,遇到task节点,需要指定用户去办理任务 ,通常不会使用 signalExecutionById, 而会去由特定用户办理任务,是流程自动流转

1.4.1. 查看当前流程实例的任务
processEngine.getTaskService().createTaskQuery().executionId(executionId).list(); 
1.4.2. 查看我的个人任务

要求在设计流程时,需要为 节点执行 assignee的属性 (指定任务办理人)

processEngine.getTaskService().findPersonalTasks(userId); 
1.4.3. 办理个人任务
processEngine.getTaskService().completeTask(taskId); 
// 这里 taskId 任务编号, jbpm4_task表主键DBID 
// 在办理任务,流程自动向后流转 

1.5. 流程变量管理

业务流转时,产生一些业务数据,这些数据要和流程实例进行关联

1.5.1. 流程变量读写操作

第一种: 启动流程实例,关联变量

process.getExecutionService().startProcessInstanceByKey(pdKey, variables);

第二种: ExecutionService 结合 executionId 读写变量

process.getExecutionService().getVariable(executionId, variableName) ;
process.getExecutionService().setVariable(executionId, variableName,variableValue) 

第三种: TaskService 结合 taskId 读写变量

process.getTaskService().getVariable(taskId, variableName) ;
process.getTaskService().setVariables(taskId, variables); 
1.5.2. 向流程实例关联复杂对象

JBPM流程变量 支持复杂对象
Long类型主键的PO对象
String 类型主键的PO对象
实现Serializable接口的对象

2. JPDL流程定义语言

jPDL定义jPDL(JBoss jBPM Process Definition Language)是构建于jBPM框架上的流程语言。

这里写图片描述

devguide 开发手册 (JBPM的项目中使用)
javadocs API手册
userguide 用户手册 (JBPM 本身使用)

先学习 userguide ,再学习devguide
目标: 掌握JBPM 六个Service 作用

JPDL 将流程中活动节点分为两大类
控制流程活动节点 Control flow activities
自动流转活动节点 Automatic activities

重点学习控制流程活动节点 Control flow activities

这里写图片描述

2.1. process节点(jpdl文件的根节点)

每次发布一个流程定义
jbpm4_deployment 存放发布信息
jbpm4_lob 存放上传资源文件
jbpm4_deployprop 存放流程定义属性信息

这里写图片描述

注意: 如果指定key 和 version 有注意事项

name 和key,如果name和之前流程相同,key也必须和之前流程相同,反之 如果key和之前流程相同,name也必须和之前的流程相同 

name 用于显示,通常写中文
key 用于编码,通常写英文 

org.jbpm.api.JbpmException:

error: invalid name '测试流程' in process 测试流程.  Existing process has name 'process' and key 'process' 
error: invalid name '测试流程' in process 测试流程.  Existing process has name 'process' and 

如果指定version,切记不要和之前相同key、相同version流程冲突,通常在开发中不指定version 。

这里写图片描述

在JBPM中流程定义编号,必须是唯一的,pdId 是由 pdkey和pdversion 组成的

2.2. transition节点(流转节点)

transition 含有流出的含义 ,表示从一个节点流向另一个节点

开始活动只能有一个transition
结束活动,不能有transition
其它活动节点,可以有一个或者多个transition

<task name="task1" g="214,114,92,52">
      <!-- 在一个节点如果含有多个transition,只有一个transition 可以不写name属性 (默认transition) -->
      <transition to="task2" g="-53,-17"/> <!-- 默认transition -->
      <transition name="to task3" to="task3" g="-53,-17"/>
</task>
// 向后流转
executionService.signalExecutionById("transition.70001");

如果没有指定向哪里流转,执行默认transition (没有name属性 )— task2

可以在流转时,指定模板transition的name属性

这里写图片描述

// 指定transitionName的流转 
executionService.signalExecutionById("transition.90001", "to task3");

2.3. start和end 节点

一个流程中只能有一个开始节点 ,可以有多个结束节点

实际开发中,没有必要画太多 结束节点, 可以在流程任意位置 中止流程
中止流程代码

processEngine.getExecutionService()
    .endProcessInstance(processInstanceId, ProcessInstance.STATE_ENDED);

2.4. state节点 (状态节点、等待节点)

state出现等待效果,不是人来办理的 ,是由程序回调,自动触发

这里写图片描述

<process name="state" xmlns="http://jbpm.org/4.4/jpdl">
   <start name="start1" g="199,26,48,48">
      <transition name="to 去网银给支付宝" to="去网银给支付宝" g="-107,-17"/>
   </start>
   <end name="end1" g="198,289,48,48"/>
   <task name="去网银给支付宝" g="149,111,159,51">
      <transition name="to 等待支付宝回应" to="等待支付宝回应" g="-107,-17"/>
   </task>
   <state name="等待支付宝回应" g="154,186,132,52">
      <transition name="to end1" to="end1" g="-47,-17"/>
   </state>
</process>

注意: 这里回应,由程序自动完成的 ,不需要人工干预 ,不适合使用task节点

问题: state节点和task节点有何不同 ?
Task节点 是任务节点,需要人工办理,办理任务后 TaskService.completeTask(taskId)
流程自动流转

State节点 是等待节点,不需要人工干预,当程序满足特定条件 ,自动向后流转
ExecutionService.signalExecutionById(executionId)

2.5. decision节点 (条件判断节点)

两种设置条件的方式
方式一 : 通过 expression 属性变量 设置条件表达式
方式二 : 通过 指定 DecisionHandler 实现程序,控制条件流转

这里写图片描述

第一种 通过 expression 属性指定 该流转到哪个分支

<decision name="exclusive1" g="236,151,48,48" expr="#{condition}">
      <transition name="to 学生半票" to="学生半票" g="-71,-17"/>
      <transition name="to 军人半票" to="军人半票" g="-71,-17"/>
      <transition name="to 领导免费" to="领导免费" g="-71,-17"/>
      <transition name="to 正常收费" to="正常收费" g="-71,-17"/>
</decision>

当程序运行 decision节点,需要有一个流程变量condition , 使用condition的值与transition的name去匹配,和哪个transition 就会流向哪个transition

在购买火车票设置流程变量

// 设置流程变量
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("condition", "to 领导免费");
processEngine.getTaskService().setVariables("10008", variables);

第二种 : 通过Handler程序控制流转

  <decision name="exclusive1" g="236,151,48,48">
      <handler class="cn.itcast.jbpm.b_handler.MyDecisionHandler"></handler>
     <transition name="to 学生半票" to="学生半票" g="-71,-17"/>
     <transition name="to 军人半票" to="军人半票" g="-71,-17"/>
     <transition name="to 领导免费" to="领导免费" g="-71,-17"/>
     <transition name="to 正常收费" to="正常收费" g="-71,-17"/>
  </decision>

提供 handler程序

public class MyDecisionHandler implements DecisionHandler {
    @Override
    // 参数 openExecution,作用是用来读写流程变量
    public String decide(OpenExecution openExecution) {
        return "to 学生半票";
    }
}

原理当运行判断节点,执行handler中decide方法,用decide方法返回值和 transition的name属性比,和哪个一致,就流向哪个transition

注意:如果同时配置了expression与Handler,则expression有效,忽略Handler
handler代码比 expression表达式更加灵活

2.6. fork、join节点(分支聚合节点)

为了解决流程中并发操作,设计的节点
企业会签业务 ,当前一个流程节点,需要多个任务公共签字才能完成 !

fork出现后,程序节点中必须提供 join节点 !

这里写图片描述

jbpm4_execution 表中每个分支,产生一个子流程实例
* jbpm4_hist_procinst 在历史记录表中 不会保存子流程实例运行信息
分支使流程实例 具有多个当前任务节点 !!!

2.7. task节点 (任务节点)

何为任务节点: 在业务流程中,需要特定人来办理的活动节点,所有工作流引擎都是基于任务办理和form表单提交 来实现业务流程的自动流转的!

任务的分类:有两种
个人任务 (有指定某个人 才能完成的任务 )
组任务 (可以由一组人中任何一个人 ,进行完成的任务 )

2.7.1. 个人任务操作

三种个人任务指定方式

这里写图片描述

 <task name="员工提交申请" g="207,91,92,52" assignee="张三">
    <transition name="to 部门经理审批" to="部门经理审批" g="-95,-17"/>
 </task>
 <task name="部门经理审批" g="211,177,92,52" assignee="#{manager}">
    <transition name="to 总经理审批" to="总经理审批" g="-83,-17"/>
 </task>
 <task name="总经理审批" g="214,249,92,52">
      <assignment-handler class="cn.itcast.jbpm.b_handler.MyPersonlTaskHandler" />
    <transition name="to end1" to="end1" g="-47,-17"/>
 </task>

个人任务操作 :
查看我的个人任务
办理个人任务

员工提交申请,为员工指定负责部门经理

// 为员工指定负责的经理
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("manager", "老王");
processEngine.getTaskService().setVariables("8", variables);

运行总经理审批,Handler执行

public class MyPersonlTaskHandler implements AssignmentHandler {
    @Override
    // Assignable对象 用来指定任务负责人
    // OpenExecution 用来读写流程变量
    public void assign(Assignable assignable, OpenExecution openExecution) throws Exception {
        // 指定任务负责人
        assignable.setAssignee("老毕");
    }
}

重点: 在流程执行过程中,为个人任务节点,更换 任务负责人 !
TaskService 提供

这里写图片描述

2.7.2. 组任务操作

由一组人 都可以完成的任务

组任务分为两大种分配方式 :
candidate-users 属性进行分配
candidate-groups 属性进行分配

在 AssignmentHandler 中 为任务指定组用户

这里写图片描述

<task name="填写财务报销申请" g="216,86,123,52" candidate-users="张三,李四,王五">
   <transition name="to 经理审批" to="经理审批" g="-71,-17"/>
</task>
<task name="经理审批" g="235,171,92,52" candidate-groups="经理">
   <transition name="to 财务拨款" to="财务拨款" g="-71,-17"/>
</task>
<task name="财务拨款" g="236,246,92,52">
      <assignment-handler class="cn.itcast.jbpm.b_handler.MyGroupTaskHandler" />
   <transition name="to end1" to="end1" g="-47,-17"/>
</task>

备注: candidate-users 为任务一次指定多个用户 (张三,李四,王五 是用户编号), candidate-groups为任务一次指定多个组 (经理 是 组编号) ,assignmentHandler 是通过程序方式,为任务指定组用户

启动流程后,进入“填写财务报销申请”节点,在jbpm4_task表存在记录

这里写图片描述

这里写图片描述

ASSIGNEE 为 null,说明这个任务 不是某个人的 个人任务

在jbpm4_participation 表中,查看组任务的信息

这里写图片描述

组任务的常见操作 :

1、查询组任务

这里写图片描述

// 查询组任务 jbpm4_participation 表
List<Task> list = processEngine.getTaskService().findGroupTasks("李四");
System.out.println(list.size());
for (Task task : list) {
    System.out.println("任务编号:" + task.getId());
    System.out.println("任务名称:" + task.getName());
}

2、拾取组任务 (将组任务变为个人任务 )

这里写图片描述

// 拾取组任务,变为个人任务, jbpm4_task 表 ASSIGNEE 栏有值
processEngine.getTaskService().takeTask("8", "王五");

改变 task表 ASSIGNEE 栏值

这里写图片描述

变为个人任务后,按照个人任务的操作,进行查询和办理

3、 如果王五拾取组任务,不能办理,必须更换任务的负责人

这里写图片描述

// 改变任务的负责人,为 李四
processEngine.getTaskService().assignTask("8", "李四");

// 如果把任务负责人 设置 null , 将任务重新放回到组中,成为组任务
processEngine.getTaskService().assignTask("8", null);

办理 填写报销申请任务,流转 经理审批 节点

 <task name="经理审批" g="235,171,92,52" candidate-groups="经理"> 

jbpm4_participation 出现组用户信息

这里写图片描述

GROUPID_ 为经理,这个组任务由 经理这个组内用户来完成 !

问题: 经理这个组中有哪些用户 ??

这里写图片描述

这三张表 用来存放 JBPM系统组和用户的信息的
jbpm4_id_group 存放组信息
jbpm4_id_user 存放用户信息
jbpm4_id_membership 存放组和用户的关系信息

需要 向JBPM系统内,添加一些 组和用户的信息 ! 使用 IdentityService

这里写图片描述

// 创建组
identityService.createGroup("经理");
// 创建用户
identityService.createUser("1", "张", "老");
identityService.createUser("2", "王", "老");
identityService.createUser("3", "李", "老");
// 创建关系
identityService.createMembership("1", "经理");
identityService.createMembership("2", "经理");
identityService.createMembership("3", "经理");

办理经理审批任务, 流转 财务拨款 ,执行指定handler程序

public class MyGroupTaskHandler implements AssignmentHandler {
    @Override
    public void assign(Assignable assignable, OpenExecution openExecution) throws Exception {
        // 向系统指定组用户
        assignable.addCandidateUser("小丽"); // candidate-users
        assignable.addCandidateUser("小明");

        assignable.addCandidateGroup("财务");// candidate-groups
    }
}

这里写图片描述

为一个已经存在的任务,临时添加组用户
第一种, 将新用户加入 group 组内 (财务)
第二种,

processEngine.getTaskService().addTaskParticipatingUser(taskId,userId, Participation.CANDIDATE);

这里写图片描述

3. 任务泳道swimlanes的使用

JBPM 绘制流程设计图 ,是什么图 ? —- UML 活动图

UML 统一建模语言,提供九种图 用来进行软件 设计和分析

用例图 — 需求分析
类图 描述程序类结构,用于设计阶段
时序图(序列图) 用于分析和设计
活动图

这里写图片描述

泳道的作用,在流程节点中,指定一些节点由特定一个人或者一组人来完成 !

问题 :

<task name="报销申请" g="188,81,92,52" candidate-users="张三,李四,王五">
   <transition name="to 发钱" to="发钱" g="-47,-17"/>
</task>
<task name="发钱" g="192,165,92,52">
   <transition name="to 签字" to="签字" g="-47,-17"/>
</task>
<task name="签字" g="196,237,92,52" candidate-users="张三,李四,王五">
   <transition name="to end1" to="end1" g="-47,-17"/>
</task>

如果 报销申请和 签字,都使用组任务,申请和签字可能会出现不是一个人完成的 !
如何保证,申请的人和签字的人 是同一个人呢 ? —- 泳道

<!-- 保证 申请和签字是同一个 人  -->
 <swimlane name="commitperson" candidate-users="张三,李四,王五">
</swimlane>
 <task name="报销申请" g="188,81,92,52" swimlane="commitperson">
    <transition name="to 发钱" to="发钱" g="-47,-17"/>
 </task>
 <task name="发钱" g="192,165,92,52">
    <transition name="to 签字" to="签字" g="-47,-17"/>
 </task>
 <task name="签字" g="196,237,92,52" swimlane="commitperson">
    <transition name="to end1" to="end1" g="-47,-17"/>
 </task>

提交申请时,变为个人任务后, 到达签字环节时,不再是组任务,直接变为申请人的个人任务!

4. JBPM和项目整合

4.1. 导入jar包

如果使用不是maven的项目, 将之前使用 JBPM的jar包 (挑选不重复的放入你的项目)
如果项目使用 maven管理,使用坐标导入jar包

在pom.xml 先配置仓库

<repositories>
        <!--JBPM依赖包 maven2资源库 -->
        <repository>
            <id>Jboss-JBPM-Repositories</id>
            <name>JbossJBPM</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>
        </repository>
</repositories>

使用坐标导入jar包

<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-api</artifactId>
    <version>4.4</version>
</dependency>
<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-jpdl</artifactId>
    <version>4.4</version>
</dependency>
<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-pvm</artifactId>
    <version>4.4</version>
</dependency>
<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-log</artifactId>
    <version>4.4</version>
</dependency>
<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-bpmn</artifactId>
    <version>4.4</version>
</dependency>

JBPM 运行需要 Javamail

<!-- javamail -->
<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4</version>
</dependency>

4.2. 配置文件导入

项目 已经使用spring 文件 配置 hibernate 属性
参考 : JBPM devguide 开发手册

1、 将 jbpm.cfg.xml 复制 src/main/resources 目录

2、 修改jbpm.cfg.xml

 <import resource="jbpm.tx.hibernate.cfg.xml" /> 

换为

 <import resource="jbpm.tx.spring.cfg.xml" />

3、 将原来 hibernate 配置写入spring 配置文件
修改 applicationContext-common.xml
修改mysql方言

<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> 

在配置文件引入JBPM的hbm文件

<!-- 映射JBPM的 hbm -->
<property name="mappingResources">
    <list>
        <value>jbpm.repository.hbm.xml</value>
        <value>jbpm.execution.hbm.xml</value>
        <value>jbpm.history.hbm.xml</value>
        <value>jbpm.task.hbm.xml</value>
        <value>jbpm.identity.hbm.xml</value>
    </list>
</property>

4、 通过 JBPM提供工具类,将ProcessEngine 流程引擎对象配置为 Spring 容器中Bean
抽取 applicationContext-jbpm.xml
配置整合JBPM

<!-- 整合jbpm 配置 -->
<bean id="springHelper" class="org.jbpm.pvm.internal.processengine.SpringHelper">
    <property name="jbpmCfg" value="jbpm.cfg.xml"></property>
</bean>
<bean id="processEngine" factory-bean="springHelper"
    factory-method="createProcessEngine" />

编写测试用例,发布一个流程定义 !

这里写图片描述

4.3. 在使用tomcat运行项目时,存在jar冲突问题

问题一: 在打成war包 部署 项目时
启动tomcat 报错

16:13:33,031 ERROR ContextLoader:307 - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean wit
h name 'sessionFactory' defined in class path resource [applicationContext-commo
n.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMe
thodError: org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/Stati
cLoggerBinder;

原因分析 : slf4j-jdk 的jar 和 slf4j-log4j的jar 冲突

这里写图片描述

<dependency>
    <groupId>org.jbpm.jbpm4</groupId>
    <artifactId>jbpm-pvm</artifactId>
    <version>4.4</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-jdk14</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

问题二:

 javax.servlet.ServletException: java.lang.LinkageError: loader constraint violation: when resolving interface method "javax.servlet.jsp.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory;" the class loader (instance of org/apache/jasper/servlet/JasperLoader) of the current class, org/apache/jsp/index_jsp, and the class loader (instance of org/apache/catalina/loader/StandardClassLoader) for resolved class, javax/servlet/jsp/JspApplicationContext, have different Class objects for the type p.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory; used in the signature

原因分析 :
Jbpm导入 juel-api的jar 和 tomcat/lib/el-api.jar 冲突
解决: 将 juel-*.jar 三个jar 移动tomcat/lib 下,删除webapp项目中对应jar包

4.4. 解决maven运行tomcat jar冲突问题

第一步 : 不要发布juel-*.jar 三个jar包

这里写图片描述

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>juel</groupId>
            <artifactId>juel-api</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>juel</groupId>
            <artifactId>juel-impl</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>juel</groupId>
            <artifactId>juel-engine</artifactId>
            <version>2.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
 </dependencyManagement>

第二步: 在目标tomcat 引入juel的jar

<!-- 使用 plugin 配置tomcat-maven-plugin -->
 <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>tomcat-maven-plugin</artifactId>
    <version>1.1</version>
    <!-- 对插件进行配置 -->
    <configuration>
        <!-- 内嵌插件 端口 -->
        <port>80</port>
    <!-- 远程发布  -->
    <url>http://localhost:8080/manager/text</url>  
    <server>myserver</server>
    </configuration>
    <!-- 引入 juel jar 包  -->
        <dependencies>
            <dependency>
                <groupId>de.odysseus.juel</groupId>
                <artifactId>juel-api</artifactId>
                <version>2.2.6</version>
            </dependency>
            <dependency>
                <groupId>de.odysseus.juel</groupId>
                <artifactId>juel-impl</artifactId>
                <version>2.2.6</version>
            </dependency>
        </dependencies>
 </plugin>

其它

课前资料

这里写图片描述

课后资料

这里写图片描述

课程视频内容

这里写图片描述

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值