jBPM-4.0中文开发指南

 

第 1 章 简介

 

    1.1. 目标读者

    这个开发指南是为了给有经验的开发者看的, 这样就可以获得jBPM的完全的灵活性。在这个开发文档中提及的特性 可能不会被支持到。请自行使用。

    1.2. 源代码和WIKI

    jBPM的源代码可以在我们的SVN获得:

    https://anonsvn.jboss.org/repos/jbpm/jbpm4/

    这里有一篇关于如何构建源代码的wiki:

    http://www.jboss.org/community/docs/DOC-12867

     jBPM的WIKI地址在:

    http://www.jboss.org/community/docs/DOC-11184

    1.3. Maven仓库

    你可以使用发布包中包含的jBPM和对应的依赖库。发布包中的jbpm.jar 包含了许多jBPM模块中的泪:jbpm-api, jbpm-log, jbpm-test-base, jbpm-pvm, jbpm-jpdl和jbpm-enterprise. 所以单独使用发布包中的jbpm.jar 不会在编译时区分API类和实现类。 如果你想只依赖jBPM的API,来构建一个自己的工程, 可以直接使用我们的仓库。它的地址在: http://repository.jboss.com/maven2/org/jbpm/jbpm4/

 

 

 

 第 2 章 流程虚拟机

 

    为了通过插件方式容纳多种流程语言和活动,jBPM基于了流程虚拟机。 本质上,流程虚拟机是一个特定的可执行图形的框架。 一个流程定义表现为一个执行流, 它拥有可以表现为图形的一种结构。

    流程虚拟机将流程定义从活动行为中切分了出来。 流程虚拟机从一个活动到下一个获得获取可执行的流程, 并将活动的行为委派给可插拔的Java类。 这里有一个API(ActivityBehaviour)用来作为 流程虚拟机和活动行为代码的接口。像jPDL这类的语言仅仅是 一系列活动行为的实现和解析器。

    流程定义实例

    图 2.1. 流程定义实例

    一般来说流程定义都是静态的。一个流程定义由活动和转移组成。 一个活动运行时的行为被封装在一起, 它是来自流程图形结果的一部分。

    流程结构类图

    图 2.2. 流程结构类图

    流程虚拟机没有包含任何活动实现。 它只提供了执行环境和活动API, 可以当做Java组件来编写ActivityBehaviour的实现。 活动也可以使等待状态。 这意味着活动的控制流会来自于流程系统的外部。 比如一个人工任务或者异步服务调用。当执行处于等待状态, 执行的运行时状态可以保存到一个数据库中。

    一个流程定义可以启动多个执行。 一个执行是一个指向当前活动的指针。

    执行实例

    图 2.3. 执行实例

    为了展示执行的同步路径, 这里是一个父子继承关系, 这样一个流程实例可以复制执行的同步路径。

    执行类图

    图 2.4. 执行类图

 

 

 

第3 章 配置

 

    jbpm.jar包含了一些默认配置文件, 它们可以导入到用户配置文件中。

    这样,用户很容易选择包含或排除哪些功能。 而且这些配置信息也包含了实现, 所以用户可以只导入那些起作用的配置文件, 当我们发布的配置文件中出现了修改的时候。

    配置文件可以导入到用户的jbpm.cfg.xml中:

 

jbpm.default.cfg.xml
jbpm.identity.cfg.xml
jbpm.jbossremote.cfg.xml
jbpm.jobexecutor.cfg.xml
jbpm.tx.hibernate.cfg.xml
jbpm.tx.jta.cfg.xml

 

    jbpm.default.cfg.xml:包含了默认的配置, 像无法,hibernate配置(来自jbpm.hibernate.cfg.xml中的配置), hibernate会话工厂,业务日历等等。

    一个标准的java配置看起来像是这样:

 

<?xml version="1.0" encoding="UTF-8"?>

<jbpm-configuration>

  <import resource="jbpm.default.cfg.xml" />
  <import resource="jbpm.tx.hibernate.cfg.xml" />
  <import resource="jbpm.jpdl.cfg.xml" />
  <import resource="jbpm.identity.cfg.xml" />
  <import resource="jbpm.jobexecutor.cfg.xml" />

</jbpm-configuration>

 

    在一个JTA环境中,使用jbpm.tx.jta.cfg.xml 替换jbpm.tx.hibernate.cfg.xml.

    如果希望自定义这些配置中的任何部分,可以在jbpm.cfg.xml中 使用自定义的内容替换引用部分。

    jbpm.jar也包含了下列hibernate映射配置文件:

 

jbpm.execution.hbm.xml
jbpm.history.hbm.xml
jbpm.identity.hbm.xml
jbpm.repository.hbm.xml
jbpm.task.hbm.xml
jbpm.jpdl.hbm.xml

 

    所有这些将java领域模型映射到一个关系数据库中。

    jbpm.jar还包含的其他配置文件:

 

jbpm.task.lifecycle.xml
jbpm.variable.types.xml
jbpm.wire.bindings.xml
jbpm.jpdl.activities.xml
jbpm.jpdl.eventlisteners.xml

 

    如何从配置文件开始进行解析,参考

    ●  类 org.jbpm.pvm.internal.env.JbpmConfigurationParser

    ●  资源 modules/pvm/src/main/resources/jbpm.wire.bindings.xml

    ●  包 modules/pvm/src/main/java/org/jbpm/pvm/internal/wire/binding

 

 

 

第 4 章 架构

    4.1. APIs

    流程虚拟机包含4个集成的API,在不同的执行模式下, 覆盖完整的流程工作。 每个API都有特定的目的, 满足下面的架构。

    流程虚拟机中的4个API

    图 4.1. 流程虚拟机中的4个API

    服务接口用在应用代码中,与流程虚拟机进行交互, 它将运行在支持事务的持久化模式下,后端基于数据库。 这是用户将PVM作为一个工作流引擎使用的最常用的方式。

    如果不想使用持久化方式执行流程,可以直接使用客户端API来处理流程和执行对象。 客户端API对外暴露了核心模型对象的方法。

    活动API用来实现活动在运行时的行为。 因此一个活动类型实际上是一个组件,核心是实现了ActivityBehaviour接口。 活动行为实现可以控制执行的流程。

    事件监听器API用来编写java代码,它可以用来处理流程事件。 它比活动API类似, 唯一的差别是事件监听器不能控制执行的流程。

    4.2. 活动API

    活动API允许使用java实现运行时的活动行为。

 

public interface ActivityBehaviour extends Serializable {
  void execute(ActivityExecution execution) throws Exception;
}

 

    一个活动就是分配给活动的一些行为。 提供的执行时到达这个活动的执行。 ActivityExecution接口 暴露了控制执行流程的方法。

 



  void waitForSignal();
  void take(String transitionName);
  void execute(String activityName);

  ...

}

 

 

 

4.3. 事件监听API

    事件监听API允许使用java开发监听器, 并在特定的流程事件发生时调用,像进入一个活动或离开一个活动。 它与活动API类似, 不同的是不能控制执行流程的传播。 比如,当一个执行选择了一个转移,一个对应的监听器会被激活, 但是因为这个转移已经被选择了, 执行的流程无法被事件监听器改变。

 

public interface EventListener extends Serializable {

  void notify(EventListenerExecution execution) throws Exception;

}

 

    4.4. 客户端API

    客户端API是一套暴露了相关方法的接口, 它用来直接管理流程定义上的执行和执行对应。

    最小的需求,客户端API和活动API需要使用活动创建 流程定义并执行它。

    4.5. 环境

    在持久化执行环境下,环境的第一目的 是让流程在不同的事务环境下执行, 比如Java标准版,Java企业版,SEAM和Spring.

    PVM代码自身只通过自身定义的接口来调用事务资源。 比如,PVM自身拥有一些建立在hibernate会话,异步消息会话 和定时任务会话的接口方法。

    环境允许为其配置真实的实现, 在请求的基础上实现服务的延迟加载, 为事务的持续获得服务对象。

    一个环境工厂是静态的,一个环境工厂 提供应用中的所有线程。

 

EnvironmentFactory environmentFactory = new PvmEnvironmentFactory("environment.cfg.xml");

 

    环境部分可以像这样 围绕在持久化流程操作周围:

 

Environment environment = environmentFactory.openEnvironment();
try {

  ... inside the environment block...

} finally {
  environment.close();
}

 

    PVM自身会从环境中获得所有事务资源和配置。 Activity实现 也可以做同样的事情。

    org.jbpm.pvm.internal.cfg.JbpmConfiguration 这个类扮演着Configuration, ProcessEngine和EnvironmentFactory三个角色。

    4.6. 命令

    命令封装了将被运行在环境块中的操作。 命令的主要目的是获得逻辑。

 



  T execute(Environment environment) throws Exception;

}

 

 

 4.7. 服务

    这里有三个主要服务:RepositoryService, ExecutionService和ManagementService. 通常来说,服务是会话门面,用来暴露PVM持久化应用的方法。 下一部分用例子展示 这些服务中的基本方法。

    RepositoryService管理 流程定义的资源。

 

public interface RepositoryService {

  Deployment createDeployment();

  ProcessDefinitionQuery createProcessDefinitionQuery();

  ...

}

 

    ExecutionService管理 运行时的执行。

 

public interface ExecutionService {

  ProcessInstance startProcessInstanceById(String processDefinitionId);

  ProcessInstance signalExecutionById(String executionId);

  ...

}

 

    ManagementService包含了所有管理操作 来保持系统启动运行。

 

public interface ManagementService {

  JobQuery createJobQuery();

  void executeJob(long jobDbid);

  ...

}

 

    所有这些方法都封装成Command. 这三个服务执行的方法 都委派给一个CommandService:

 

public interface CommandService {

  <T> T execute(Command<T> command);

}

 

    CommandService被配置到环境中。 一个CommandService链可以看做环绕在一个命令周围的一些拦截器。 这就是如何在不同的环境下 进行持久化和事务支持的核心机制。

    默认的配置文件jbpm.default.cfg.xml 包含了下面的配置服务。

 

<jbpm-configuration>

  <process-engine>

    <repository-service />
    <repository-cache />
    <execution-service />
    <history-service />
    <management-service />
    <identity-service />
    <task-service />

 

    And the file jbpm.tx.hibernate.cfg.xml contains the following command service configuration:

 

<jbpm-configuration>

  <process-engine-context>
    <command-service>
      <retry-interceptor />
      <environment-interceptor />
      <standard-transaction-interceptor />
    </command-service>
  </process-engine-context>

  ...

 

    这些服务,比如repository-service,execution-service 和management-service将按照类型找到配置好的command-service. command-service标签符合默认的命令服务, 基本上什么也不做, 只是在提供给它的环境上执行命令。

    配置的command-service结果, 在默认的命令执行期下面的三个拦截器链中。

    CommandService拦截器

    图 4.2. CommandService拦截器

    retry拦截器是链中的第一个,它会被环境 当做CommandService.class暴露出来。 所以retry拦截器会分别提供给repository-service, execution-service和management-service这些服务。

    retry-interceptor会获取hiberate的StaleObjectExceptions (因为乐观锁失败)并重新尝试执行命令。

    environment-interceptor会把一个环境块 放到命令执行的周围。

    standard-transaction-interceptor会初始化一个 StandardTransaction.hibernate会话/事务会被作为 标准事务的一个资源。

    这个拦截器栈的不同配置也可以使用:

    ●  把执行委派到一个本地ejb命令服务, 这样可以启动一个内容管理的事务。

    ●  把执行委派到一个远程ejb命令服务, 这样命令实际执行在一个不同的JVM上。

    ●  把命令打包成一个异步消息, 这样命令会异步执行在一个不同的事务中。

 

 

 

第 5 章 实现基本活动

    这一章解释了流程定义的基础,流程虚拟机给予的功能 以及活动实现是如何构建的。 同时,客户端API被用来执行包含了那些活动实现的流程。

    5.1. ActivityBehaviour

    PVM库没有包含完整的流程结构。 作为替代的是,活动的运行时行为被委派给一个ActivityBehaviour. 换句话讲,ActivityBehaviour是一个接口, 它用来在纯java环境实现流程结构的运行时行为。

 

public interface ActivityBehaviour extends Serializable { 

  void execute(ActivityExecution execution) throws Exception; 

}

 

    当一个活动行为被调用时,它就处于执行传播的全部控制中。 换句话说,一个活动行为可以决定下一步应该执行什么执行。 比如,可以使用execution.take(Transition)获得一个转移, 或者使用execution.waitForSignal()进入等待阶段。 万一活动行为没有调用任何上述的执行传播方法, 执行将 按默认方式执行。

    5.2. ActivityBehaviour实例

    我们会启动一个非常原始的hello world例子。 一个Display活动会将一条信息打印到控制台:

 

public class Display implements ActivityBehaviour { 

  String message; 

  public Display(String message) { 
    this.message = message; 
  } 

  public void execute(ActivityExecution execution) { 
    System.out.println(message); 
  } 
}

 

    让我们使用这个活动构建我们第一个流程定义:Display实例流程

    

    图 5.1. Display实例流程

    TODO add ProcessBuilder example code

    现在我们可以像下面这样执行流程:

 

Execution execution = processDefinition.startExecution();

 

    startExecution的调用会在控制台打印hello world:

    hello

    world

    一个总是值得提醒的事情是活动可以使用属性进行配置。 在Display例子中,你可以看到message属性在两种使用方法中配置的不同。 通过配置属性,我们可以写出可复用的活动。 它们可以在以后每次使用在流程中都进行不同的配置。 这是一个基本的部分, 将流程语言构建在流程虚拟机之上。

    其他需要解释的部分是 这个活动实现没有包含任何执行传播的功能。 当一个新流程实例启动时, 执行会定位到初始活动,那个活动会被执行。 Display.execute方法用来决定默认的执行传播。 具体的,这意味着活动自己 没有调用任何执行传播的方法。 那种情况下,默认的传播会执行。默认传播会选择第一个转移,如果这个转移存在的话。 如果没有,它会结束这个执行。 这揭示了为什么a活动和b活动都被执行, 而在b活动执行完执行会停止。

    关于默认流程行为的更多细节可以 在第 7.3 节 “默认执行行为”找到。

    5.3. ExternalActivityBehaviour

    外部活动是负责流程执行由外部转移进来的活动, 外部的意思是来自流程系统的外部。 这意味着这个执行流程对于系统来说,这是一个等待状态。 这个执行会一直等待到外部触发器调用。

    为了处理外部触发器,ExternalActivityBehaviour 为ActivityBehaviour添加了一个方法:

 

public interface ExternalActivity extends Activity { 

  void signal(Execution execution, 
              String signal, 
              Map<String, Object> parameters) throws Exception; 

}

 

    就像普通的活动,当一个执行到达一个活动, 外部活动行为的execute方法会被调用。 在外部活动中,execute方法会传递另一个系统的响应, 然后通过调用execution.waitForSignal() 进入等待状态。 比如在execute方法中,响应可能是由一个人传入, 通过在任务管理系统中创建一个任务入口, 然后等待到这个人完成这个任务。

    一旦活动行为已经处于等待状态, 然后执行会等待到调用signal方法。 执行会委派signal给ExternalActivityBehaviour对象 分配给当前的活动。

    所以活动的signal方法 会在等待期间,在执行获得一个外部触发器的时候调用。 signal方法中,响应会传递给后面的流程执行。 比如,当一个人完成了一个任务,任务管理系统 会在执行中调用signal方法。

    一个signal可选择使用signal名字和一个参数map. 活动行为拦截signal和参数的最常用方式是 signal对应选择的外出转移, 参数作为执行中的变量。但那些只是例子, 它一直等到活动使用singal和它期望的参数。

 

 

 

5.4. ExternalActivity实例

    这里是一个简单等待状态实现的第一个例子:

 

public class WaitState implements ExternalActivity { 

  public void execute(ActivityExecution execution) { 
    execution.waitForSignal(); 
  } 

  public void signal(ActivityExecution execution, 
                     String signalName, 
                     Map<String, Object> parameters) { 
    execution.take(signalName); 
  } 
}

 

    execute方法调用execution.waitForSignal()。 execution.waitForSignal()的调用 会使流程执行进入等待状态, 直到一个外部触发器出现。

    signal方法使用signal参数对应的转移名称 来选择转移。所以当一个执行获得一个外部触发器, signal名称被拦截,作为外部转移的名称, 执行会被传播到那个转移上。

    这里是从a到b有一个转移的相同的流程。 这时候,两个活动的行为都是WaitState.

    外部活动实例流程

    

    图 5.2. 外部活动实例流程

 

ClientProcessDefinition processDefinition = ProcessFactory.build() 
    .activity("a").initial().behaviour(new WaitState()) 
      .transition().to("b") 
    .activity("b").behaviour(new WaitState()) 
.done();

 

    让我们为流程定义启动一个新流程实例:

 

ClientExecution execution = processDefinition.startProcessInstance();

 

    启动这个流程会执行a中的WaitState活动。 WaitState.execute会调用 ActivityExecution.waitForSignal. 所以当processDefinition.startProcessInstance()返回, 执行会一直处在a活动。

 

assertEquals("a", execution.getActivityName());

 

    然后我们提供了外部触发器, 通过调用signal方法。

 

execution.signal();

 

    execution.signal()会委派给当前活动。 所以在这种情况下就是a活动里的 WaitState活动。WaitState.signal会调用 ActivityExecution.take(String transitionName)。 当我们没有提供一个signal名称,第一个名字是null会被选中。 我们指定的a的唯一转移没有名字,所以会选中这个。 然后这个转移指向b. 当执行到达b活动, b活动中的WaitState活动会被执行。 就像我们上面看到的,执行会在b一直等待, 这时signal会返回, 离开的执行指向b活动。

 

assertEquals("b", execution.getActivityName());

 

5.5. 基本流程执行

    在下一个例子里,我们会结合自动活动和等待状态。 这里例子构建了贷款审批流程,使用WaitState 和Display活动,我们刚刚创建的。 贷款流程的图形看起来像这样:

    贷款流程

    图 5.3. 贷款流程

    使用Java构建流程图形是很乏味的事情, 因为你必须在局部变量中跟踪所有的引用。 为了解决这个问题,流程虚拟机提供了一个ProcessFactory. ProcessFactory是一种领域特定语言(DSL),可以嵌入到Java中, 简化流程图形的结构。这个模型也叫做 流畅接口。

 

ClientProcessDefinition processDefinition = ProcessFactory.build("loan")
  .activity("submit loan request").initial().behaviour(new Display("loan request submitted"))
    .transition().to("evaluate")
  .activity("evaluate").behaviour(new WaitState())
    .transition("approve").to("wire money")
    .transition("reject").to("end")
  .activity("wire money").behaviour(new Display("wire the money"))
    .transition().to("archive")
  .activity("archive").behaviour(new WaitState())
    .transition().to("end")
  .activity("end").behaviour(new WaitState())
.done();

 

    为了了解ProcessFactory的更多细节,可以参考 api文档。 ProcessFactory的另一种选择是创建一个XML语言和一个XML解析器,来表示流程。 XML解析器可以直接实例化 org.jbpm.pvm.internal.model包中的类。 这种方式一般都被流程语言选择使用。

    初始化活动submit loan request和 wire the money活动是自动活动。 在这个例子中,wire the money活动的 Display实现 使用Java API来把信息输出到控制台上。但是读取器可以想象一个可选的 Activity实现,使用支付流程库的Java API 来实现一个真实的自动支付。

    上述流程的一个新执行可以像下面这样启动

 

ClientExecution execution = processDefinition.startProcessInstance();

 

    当startExecution方法返回时, submit loan request活动会被执行, 执行会位于evaluate活动。

    位于'evaluate'活动的执行

    图 5.4. 位于'evaluate'活动的执行

    现在,执行处在一个很有趣的点。这里有两个转移从evaluate指向外边。 一个转移叫approve 一个转移叫reject.像我们上面解释的, WaitState实现会根据执行的signal选择转移。 让我们像这样执行'approve' signal:

 

execution.signal("approve");

 

    这个approve signal会导致执行选择approve转移 它会到达wire money活动。

    在wire money活动中,信息会打印到控制台里。 因为Display没有调用execution.waitForSignal(), 也没有调用其他执行传播方法, 默认流程行为只会让执行继续, 使用向外的转移到达archive活动, 这也是一个WaitState.

    位于'archive'活动的执行

    图 5.5. 位于'archive'活动的执行

    所以只有当archive到达时, signal("approve")会返回。

    另一个signal就像这样:

 

execution.signal("approve");

 

    将让执行最终到达结束状态。

    位于'end'活动的执行

    图 5.6. 位于'end'活动的执行

 

 

 

 

5.6. 事件

    事件位于流程定义中, 一系列的EventListener可以进行注册。

 

public interface EventListener extends Serializable {

  void notify(EventListenerExecution execution) throws Exception;

}

 

    事件的目的是让开发者可以为流程添加程序逻辑, 不必改变流程图。 这是非常有价值的机制,可以促进业务分析人员和开发者之间的协作。 业务分析人员负责描述需求。 当他们使用流程图归档那些需求, 开发者可以获得这些图形,让它可执行化。 事件会非常方便,向一个流程中添加技术细节(比如一些数据库插入操作) 这些都是业务分析人员不感兴趣的东西。

    最常用的事件是由执行自动触发的:

    TODO: 在用户手册中解释事件

    事件是由流程元素和事件名称结合而成。 用户和流程语言也可以出发事件, 使用编程的方式在流程中使用fire方法。

 

public interface Execution extends Serializable {
  ...
  void fire(String eventName, ProcessElement eventSource);
  ...
}

 

    可以把一系列的EventListeners分配给一个事件。 但是事件监听器不能控制执行的流向, 因为它们仅仅是监听已经执行了的执行。 这与活动处理活动的行为是不同的。 活动行为可以响应执行的传播。

    我们会创建一个PrintLn事件监听器, 这与上面的Display活动是非常相似的。

 

public class PrintLn implements EventListener {

  String message;

  public PrintLn(String message) {
    this.message = message;
  }

  public void notify(EventListenerExecution execution) throws Exception {
    System.out.println("message");
  }
}

 

    多个PrintLn监听器 会在流程中注册。

    PrintLn监听器流程

    图 5.7. PrintLn监听器流程

 

ClientProcessDefinition processDefinition = ProcessFactory.build()
  .activity("a").initial().behaviour(new AutomaticActivity())
    .event("end")
      .listener(new PrintLn("leaving a"))
      .listener(new PrintLn("second message while leaving a"))
    .transition().to("b")
      .listener(new PrintLn("taking transition"))
  .activity("b").behaviour(new WaitState())
    .event("start")
      .listener(new PrintLn("entering b"))
.done();

 

    第一个事件演示如何为相同的事件注册多个监听器。 它们会根据它们指定的顺序依次执行。

    然后,在转椅上,这里的事件只有一种类型。 所以在那种情况下,事件类型不需要指定, 监听器可以直接添加到转移上。

    一个监听器每次都会执行,当一个执行触发事件时,如果这个监听器被注册了。 执行会作为一个参数提供给活动接口, 除了控制流程传播的方法以外, 都可以被监听器使用。

 

 

 

 

5.7. 事件传播

    事件会默认传播给最近的流程元素。 目的是允许监听器在流程定义或组合活动中 可以执行所有发生在流程元素中的事件。 比如这个功能允许为end事件在流程定义或一个组合活动中注册一个事件监听器。 这种动作会被执行,如果一个活动离开。 如果事件监听器被注册到一个组合活动中, 它也会被所有活动执行,当组合活动中出现了离开事件。

    为了清楚地显示这个,我们会创建一个DisplaySource事件监听器, 这会把leaving信息和事件源 打印到控制台。

 

public class DisplaySource implements EventListener {

  public void execute(EventListenerExecution execution) {
    System.out.println("leaving "+execution.getEventSource());
  }
}

 

    注意事件监听器的目的不是可视化,这是为什么事件监听器本身 不应该显示在图形中。一个DisplaySource事件监听器 会作为end事件的监听器添加到组合活动中。

    下一个流程展示了DisplaySource事件监听器如何 作为'end'事件的监听器注册到composite活动:

    一个在组合活动中为end事件注册了不可见的事件监听器的流程。

    图 5.8. 一个在组合活动中为end事件注册了不可见的事件监听器的流程。

 

TODO 更新代码片段

 

    下一步,我们会启动一个执行。

 

ClientExecution execution = processDefinition.startProcessInstance();

 

    在启动一个新执行后,执行将在a活动中 作为初始活动。没有活动离开,所以没有信息被记录下来。 下一个signal会给与执行, 导致它选择从a到b.

 

execution.signal();

 

    当signal方法返回,执行会选择转移 然后end事件会被a活动触发。 那个组合活动会被传播到组合活动和流程定义中。 因为我们的DisplaySource 监听器放到 composite活动中, 它会接收事件,把下面的信息打印到控制台中:

 

leaving activity(a)

 

    另一个

 

execution.signal();

 

    会选择b到c的转移。那会触发两个活动离开事件。 一个在b活动,一个在组合活动。 所以下面的几行会添加到控制台输出中:

 

leaving activity(b)
leaving activity(composite)

 

    事件传播建立在流程定义的继承组合结构中。 顶级元素总是流程定义。 流程定义包含一系列活动。每个活动可以是叶子活动或者可以是一个组合节点, 这意味着它包含了一系列内嵌活动。 内嵌活动可以被使用,比如超级状态或组合活动,在内嵌流程语言中,像BPEL.

    所以事件模型在组合活动和上面的流程定义中的功能是相似的。 想象'Phase one'模型一个超级状态作为一个状态机。 然后事件传播允许在超级状态中注册所有事件。 这个主意是继承组合响应图形展示。 如果一个'e'元素画在另一个'p'元素中, 'p'是'e'的父节点。一个流程定义拥有一系列定义活动。 每个活动可以拥有一系列内嵌活动。 一个转移的父节点就是它的源头和目的的第一个父节点。

    如果一个事件监听器对传播的事件没有兴趣, 可以在构建流程使用ProcessFactory的propagationDisabled()。 下一个流程是与上面相同的流程, 除了传播的事件会被事件监听器禁用。 图形还是一样。

    注册到'end'事件的事件监听器被禁用的流程。

    图 5.9. 注册到'end'事件的事件监听器被禁用的流程。

    使用流程工厂构建流程:

 

TODO 更新代码

 

    所以当第一个signal在流程中调用时,end事件 会再次触发在a活动上,但是现在在组合活动的事件监听器 不会被执行,因为传播的事件被禁用了。 禁用传播是单独的事件监听器的一个属性, 不会影响其他监听器。事件会一直被触发, 传播到整个父继承结构。

 

ClientExecution execution = processDefinition.startProcessInstance();

 

    第一个signal会选择从a到b的流程。 没有信息会被打印到控制台。

 

execution.signal();

 

    下一步,第二个signal会选择从b到c的转移。

 

execution.signal()

 

    还是两个end事件被触发, 就像上面分别在b和composite活动中。 第一个事件是b活动上的 end事件。 那将被传播给composite活动。 所以事件监听器不会为这个事件执行,因为它已经禁用了传播。 但是事件监听器会在composite活动上 为end事件执行。 那是不传播的,但是直接在composite活动上触发。 所以事件监听器现在会被执行 一次,为组合活动,就像下面控制台里显示的那样:

 

leaving activity(composite)

 

 

第 6 章 流程剖析

    上面我们已经简要的接触了两个主要的流程结构: 活动,转移和活动组合。 这一章研究了流程定义结构的全部可能。

    这儿基本有两个流程定义方式:基于图形和组合流程语言。 首先,流程支持这两种情况。 每个基于图形的执行和活动组合可以用来组合一些像UML超级状态的实现。 甚至,自动功能活动可以被实现, 所以它们可以使用转移和活动组合。

    逻辑流程结构的UML类图

    图 6.1. 逻辑流程结构的UML类图

    下一步我们会显示一系列的实例图形结构, 这可以组成PVM流程模型。

    任意两个活动可以使用一个转移进行连接。

    图 6.2. 任意两个活动可以使用一个转移进行连接。

     自连接。

    图 6.3. 自连接。

     组合活动是一系列内嵌活动。

    图 6.4. 组合活动是一系列内嵌活动。

    在组合中转移到一个活动。

    图 6.5. 在组合中转移到一个活动。

    从一个组合内部的一个活动转移到组合外部的一个活动。

    图 6.6. 从一个组合内部的一个活动转移到组合外部的一个活动。

继承的组合活动的转移。活动内部可以选择组合活动的转移。

    图 6.7. 继承的组合活动的转移。活动内部可以选择组合活动的转移。

    一个活动转移到外部组合。

    图 6.8. 一个活动转移到外部组合。

    从一个组合活动转移到内部组合活动。

    图 6.9. 从一个组合活动转移到内部组合活动。

    在组合活动内部的一个初始活动。

    图 6.10. 在组合活动内部的一个初始活动。

 

 

第 7 章 高级图形执行

    7.1. 循环

    活动可以实现循环,基于转移或活动组合。 循环可以包含等待状态。

    为了支持多次自动循环执行,流程虚拟机 把执行的传播从尾部递归转换成while循环。

    7.2. 子流程

    TODO: 子流程

    7.3. 默认执行行为

    当一个Activity被用作活动行为, 它可以使用下面的方法从外部控制流程:

    * waitForSignal() 
    * take(Transition) 
    * end(*) 
    * execute(Activity) 
    * createExecution(*)

    当Activity实现用做活动行为, 没有调用任何下面的流程传播方法,然后 在活动执行时,执行会使用默认执行行为。

    默认执行行为定义在下面:

    * 如果当前活动有一个默认向外转移,选择它。 
    * 如果当前活动有一个父活动,回退到父活动。 
    * 否则,结束这个执行。

    流程语言可以重写默认执行行为, 通过重写ExecutionImpl中的 proceed方法。

    7.4. 功能活动

    活动也可以用作事件监听器,被称作功能活动。 自动活动的例子是发送邮件,执行数据库更新, 生成pdf,计算平均数,等等。 所有这些都是自动活动,没有改变执行流向。 这里是这些活动如何实现:

 

public class FunctionalActivity implements Activity, EventListener { 
    public void execute(ActivityExecution execution) { 
      perform(execution); 
    } 
    public void notify(EventListenerExecution execution) { 
      perform(execution); 
    } 
    void perform(OpenExecution execution) { 
      ...do functional work... 
    } 
  }

 

    perform方法获得一个OpenExecution, 这是ActivityExecution和 EventListenerExecution的超类。 OpenExecution没有提供任何特定目的的方法, 但是依旧是当前状态,流程定义可以通过变量检验, 这包含了环境信息 对应流程执行。

    这些方法其实都不能调用执行传播方法。 所以在perform方法完成后,执行会 执行默认的方式。

 

 

7.5. 执行和线程

    这一章解释流程虚拟机如何通过客户端的线程, 把一个执行从一个等待状态带到另一个。

    当一个客户调用一个执行的一个方法(比如signal方法)。 默认,流程虚拟机会使用线程执行流程 直到它到达一个等待状态。一旦下一个等待状态到达, 这个方法会返回,客户端的线程就会返回。 这是流程虚拟机操作的默认方式。 两个更多的异步执行可以补充默认行为: 异步继续 和异步命令服务。

    下一个流程会展示基本理论。 它有三个等待状态和四个自动活动。

    有很多顺序自动活动的流程。

    

    图 7.1. 有很多顺序自动活动的流程。

    这里是如何构建流程:

 

ClientProcessDefinition processDefinition = ProcessFactory.build("automatic") 
    .activity("wait 1").initial().behaviour(new WaitState()) 
      .transition().to("automatic 1") 
    .activity("automatic 1").behaviour(new Display("one")) 
      .transition().to("wait 2") 
    .activity("wait 2").behaviour(new WaitState()) 
      .transition().to("automatic 2") 
    .activity("automatic 2").behaviour(new Display("two")) 
      .transition().to("automatic 3") 
    .activity("automatic 3").behaviour(new Display("three")) 
      .transition().to("automatic 4") 
    .activity("automatic 4").behaviour(new Display("four")) 
      .transition().to("wait 3") 
    .activity("wait 3").behaviour(new WaitState()) 
.done();

 

    让我们和你一起顺着流程的执行一起走。

 

ClientExecution execution = processDefinition.startProcessInstance();

 

    启动一个新执行意味着初始活动被执行。 所以如果一个自动活动是初始活动,这意味着第一个未命名的向外转移会被立刻选择。 这些都发生在startProcessInstance调用的内部。

    然而在这种情况下,初始活动是一个等待状态。 所以startProcessInstance方法会立刻返回, 执行会定位到初始活动'wait 1'.一个新执行会被定为到'wait 1'.

    

    图 7.2. 一个新执行会被定为到'wait 1'.

    然后一个外部触发器会执行signal方法。

 

execution.signal();

 

    像上面解释的介绍WaitState, signal会导致选择默认的转移。 转移会把执行移动到automatic 1活动,并执行它。 automatic 1中的Display活动的execute方法, 向控制台打印一行,它不会 调用execution.waitForSignal()。 因此,执行会通过选择automatic 1外部的默认转移进行执行。 在这种状态,signal方法一直阻塞着。另一个需要考虑的方式是执行方法, 像signal会使用客户端的线程 来拦截流程定义,直到到达一个等待状态。

    然后执行到达wait 2, 执行WaitState活动。那个方法会调用 execution.waitForSignal(),这会导致signal方法返回。 线程会返回到调用signal方法 的客户端。

    所以,当signal方法返回时,执行定义到wait 2.一个signal会把执行从'initial'带到'wait 2'.

    

    图 7.3. 一个signal会把执行从'initial'带到'wait 2'.

 

 

然后执行会等待一个外部触发器, 像是一个对象(更准确的是一个对象图)在内存中, 直到下一个外部触发器执行signal方法。

 

execution.signal();

 

    第二个调用的signal会直接让执行进入wait 3, 在它返回之前。

    第二个signal让执行进入'wait 3'.

    

    图 7.4. 第二个signal让执行进入'wait 3'.

    使用这个范例的好处是相同的流程定义可以在 客户执行模式中执行 (在内存内不使用持久化),就像在持久化执行模式, 依赖应用和环境。

    当在持久化模式下执行一个流程,你如何绑定 流程执行到数据库的事务上。

    持久化模式下的事务超时

    

    图 7.5. 持久化模式下的事务超时

    在大多情况下,计算工作是流程需要完成的一部分, 在外部触发器(红色部分)之后的部分,其实很少。 一般来说,处理流程执行和处理UI传递过来的请求 的事务不会超过一秒。 而业务流程中的等待状态可能超过几小时,几天甚至几年。 当等待状态启动后,线索就变得很清晰, 在等待状态启动之前,只有计算工作的完成包含在事务中。

    考虑一下这种方式: "当到达审批时,所有的自动流程需要做的是什么, 在流程系统需要等待另一个外部触发器之前?". 除非pdf需要被创建,或大邮件需要被发送, 大部分时候,它消耗的时间都是可以忽略的。 这就是为什么在默认的持久化执行模式下, 流程工作在客户端线程下执行。

    这个原因也保证着流程同步路径的情况。 当一个执行的单独路径切分成流程同步路径, 流程花在计算上的时间是可忽略的。 所以为什么分支或切分活动实现是有意义的, 目标持久化模式产生的同步路径在同一个线程中按顺序执行。 基本上它们都只是在同一个事务中的计算工作。 因为分支或切分知道每个执行的同步路径会返回,所以这只能被完成, 当出现一个等待状态的时候。

    因为这里有一个困难的概念需要掌握,我会再次使用其他词语来解释它。 从头再看一次在持久化执行模式下被流程执行创建出来的它。 如果在一个事务中,一个执行被给与一个外部触发器, 那导致执行切分成多个执行的同步路径。 然后执行在计算上的部分也可以忽略。 生成SQL的部分也可以忽略。 因为所有在同步分支上完成的功能,必须在同一个事务中完成, 这里一般没有指针在分支或切分实现, 在多个线程中产生执行的同步路径。

    为了创建可执行流程,开发者需要确切知道什么是自动活动, 什么是等待状态,哪些线程会被分配给流程执行。 对于画业务流程的业务分析人员,事件就很简单了。 对于他们画的活动,他们通常只要知道这是一个人或是一个系统响应。 但是他们通常不知道如何转换线程和事务。

    所以对于开发者,第一个任务是分析什么是流程控制的线程中需要执行的, 什么是外部的。 查找外部触发器是寻找一个流程中的等待状态的很好的开始, 就像动词和名词可以在构建UML类图中的元素的规则。

 

 

 7.6. 流程同步

    为了进行流程同步建模,在执行中这是一个父子树形结构。 这个想法是执行主路径是树的根。 流程的主路径也被称作流程实例。 当在给定流程定义上启动或创建一个新流程实例时, 执行便被创建。

    现在,因为执行的主路径和流程实例是相同对象, 这保证了用法的简单, 在没有同步情况的简单流程下。

    基本执行结构的UML类图

    

    图 7.6. 基本执行结构的UML类图

    为了建立执行的多同步路径,活动实现比如一个分支或切分 创建子执行, 使用ActivityExecution.createExecution方法。 活动实现比如结合或合并可以停止流程的这些同步路径, 通过调用执行同步的stop方法。

    只有叶子执行可以激活,非叶子执行应该不是激活的。 这个执行的树形结构没有坚持一个同步或结合行为的特殊类型。 它从事着分支或和切分 和结合或和合并来使用执行树结构, 用任何方式,他们想定义期望的同步行为。 这里我们看一个同步执行的例子。

    执行的同步路径

    

    图 7.7. 执行的同步路径

    这里有执行的一个付款和一个发货路径。 在这种情况,水平线上的活动展示了分支和结合。这个执行显示了三个执行。 执行的主路径不是激活的(显示成灰色) 执行的付款和发货路径是激活的,分别指向了 bill和ship活动。

    从事活动行为的实现,是他们想使用的执行结构。 假设多个任务必须在执行进行之前完成。 活动行为可以为这个产生一系列子执行。 或者可以选择,任务组件可以支持任务组, 分配给单独的执行。在那种情况, 任务组件成为同步任务的响应, 因此把这个责任移动到执行树形结构范围之外。

 

 7.7. 异常处理器

    在所有分配到流程的代码中,像 Activity,EventListeners和 Condition,可能分配给异常处理器。 这可以想成是把这些实现的方法实现包含在try-catch块中。 但是为了构建更多可复用的构建块, 为了委派类和异常处理逻辑, 异常处理器可以添加到核心流程模型中。

    一个异常处理器可以分配给任何流程元素。 当一个异常发生在一个委派类中,一个匹配的异常处理器就会被找到。 如果找到了一个这样的异常处理器,它会有一个处理这个异常的机会。

    如果一个异常处理器处理完成,没有出现问题,然后这个异常会 被认为是处理了,就会在委派代码调用后继续。 比如,一个转移有三个动作,第二个动作抛出一个异常, 这个异常被异常处理器处理,然后

    编写自动活动,异常处理器提醒是很容易的。 默认是任意执行。没有方法需要在执行中调用。 所以如果一个自动活动抛出一个异常,被异常处理器处理, 这个执行会在这个执行后继续执行。这对于控制流向活动 就会有一个更大的困难。它们可能需要包含try-finally块 来调用执行中对应的方法,在异常处理器 获得一个机会来处理异常。比如,如果活动是等待状态, 然后发生了一个异常,这里就会有一个风险,线程会跳出 execution.waitForSignal()的调用, 导致执行在这个活动以后继续执行。

    TODO: exceptionhandler.isRethrowMasked

    TODO: transactional exception handlers

    TODO: we never catch errors

    7.8. 流程修改

    TODO: 流程修改

    7.9. 锁定和流程状态

    一个执行的状态不是激活就是锁定。 一个激活的执行不是执行就是等待外部触发器。 如果一个执行不是STATE_ACTIVE,那么它就是被锁定。 一个锁定的执行是只读的,不能接受任何外部触发器。

    当一个新执行被创建时,它是STATE_ACTIVE. 为了把状态修改成锁定状态,使用lock(String)。一些STATE_*常量 被提供了,它们演示了最常用的锁定状态。 但是在图片中的'……'状态展示了任何字符串 都可以作为状态提供给lock方法。

    执行的状态

    

    图 7.8. 执行的状态

    如果一个执行被锁定,修改执行的方法会 抛出一个PvmException,信息会引用真实的锁定状态。 触发事件,更新变量,更新优先级,添加注释 不会当做是修改执行。 子节点的创建和删除也不会检测, 这意味着那些方法可以被外部API客户和活动行为调用, 即使执行在锁定状态。

    确保比较getState()和STATE_*常量时 使用。equals,不要使用'==',因为如果执行从持久存储加载。 会创建一个新字符串,而不是使用常量。

    一个执行实现会被锁定:

    * 当它结束 
    * 当它暂停 
    * 在异步延续过程中

    更多的,锁定可以被活动实现使用, 让执行在等待状态下只读,然后为这个执行传递 的外部实例就像这样:

    * 一个人员任务 
    * 一个服务调用 
    * 一个等待状态当探测器检测一个文件的出现时就结束

    在这些情况,策略是外部实例应该获得 执行的完全控制,因为它想要控制什么应该允许,什么不应该。 为了获得那种控制,他们锁定了执行,所以所有内部交互 必须通过外部实例传递。

    一个创建外部实例的主要原因是, 它们可以在执行已经执行过还存在。比如, 在服务调用的情况,定时器可以导致执行获得超时转移。 当响应在超时后到达,服务调用实例应该 确认它没有signal这个执行。所以服务调用可以看做 一个活动实例(活动实例) 是对活动每个执行的唯一实例。

    外部实例它们自己负责管理执行锁定。 如果定时器和客户端应用结果是选择 外部实例,而不是直接选择执行,然后在理论上是不必要的。 它是从事活动行为实现,无论它希望 执行锁定还是解锁。

 

第 8 章 软件日志

    8.1. 配置

    PVM可以使用JDK日志(java.util.logging)或log4j.当第一个信息被记录, PVM日志会根据下面的过程进行选择:

    1. 如果一个logging.properties资源被发现在 classpath(使用context classloader)下,然后JDK日志会被使用 这个文件会被用来实现JDK日志。

    2. 如果log4j在classpath中找到,然后log4j会被用到。 对log4j的检测会通过检测context classloader中的 org.apache.log4j.LogManager来实现。

    3. 如果没找到上面的,会使用JDK日志。

    8.2. 目录

    PVM类使用它们自己的类名作为日志的目录。

    为了对PVM的类在做什么进行基本的激烈, 最好打开debug级别。 trace级别对于这个目的来讲 输出似乎太多了。

    8.3. JDK日志

    在JDK日志中,debug映射为fine, trace映射为finest. finer级别没有用到。

    org.jbpm.pvm.internal.log.LogFormatter是pvm库的一部分, 它可以为日志信息创建一个很好的单行输出。 它也有一个灵活的功能,为每个线程创建一个唯一的标示。 想要配置它,这是一个常用的logging.properties

 

handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = org.jbpm.pvm.internal.log.LogFormatter

# For example, set the com.xyz.foo logger to only log SEVERE messages:
# com.xyz.foo.level = SEVERE

.level = SEVERE
org.jbpm.level=FINE
org.jbpm.tx.level=FINE
org.jbpm.pvm.internal.wire.level=FINE

 

    8.4. 调试持久化

    在测试持久化时,下面的日志配置是很有价值的。 目录org.hibernate.SQL显示执行的SQL语句, 目录org.hibernate.type显示查询中 设置的参数值。

 

org.hibernate.SQL.level=FINEST
org.hibernate.type.level=FINEST

 

    一旦你获得一个失败处理,比如因为一个hibernate异常, 你可能希望把批处理大小设置为0,像这样在hibernate配置里:

 

hibernate.jdbc.batch_size = 0

 

    也是在hibernate配置中,下面的配置允许记录 hibernate输出的SQL的日志细节:

 


hibernate.format_sql = true
hibernate.use_sql_comments = true

 

第 9 章 历史

    9.1. 概述

    在流程执行过程中都会触发HistoryEvents.

    我们在两个级别维护历史信息:流程实例和活动实例。

    流程实例启动和流程实例结束生成历史事件 会从实现中直接触发。

    ActivityBehaviour实现负责调用historyXxx方法 被ActivityExection中暴露。

    所有HistoryEvent被委派给一个HistorySession.默认的HistorySessionImpl 将调用历史事件它们自己的process()方法。

    HistoryEvent是临时事件。在process方法中,它们在历史模型中建立信息。 这是一个HistoryProcessInstance,这里还有一个从HistoryActivityInstance 开始的完全类继承结构。

    在HistoryEvent.process方法中,历史事件创建模型实体或者在模型实体中合并信息。 比如,一个ProcessInstanceStart历史事件会创建一个HistoryProcessInstance实例或记录。 ProcessInstanceEnd会设置已存在的HistoryProcessInstance实例或记录的 结束属性。

    与活动的模式相同。但是对于自动活动,这里有一个优化 所以只有一个事件被创建,所以信息被存储在一个单独的insert中 (所有这些都发生在一个事务中)。

 

第 10 章 委派类

    10.1. 什么是委派类

    委派类是实现了Activity或 Condition的类。 根据流程虚拟机的期待,这里有外部类提供了程序逻辑, 插入到PVM的图形执行中。 外派类可以被流程语言 和最终用户提供。

    10.2. 委派类的配置

    委派类可以实现配置化。成员变量可以包含配置参数, 所以一个委派类可以在 每次使用的时候进行不同的配置。比如, 在Display活动中, 打印到控制台的信息就是一个 配置参数。

    委派类应该是无状态的,这意味着执行接口方法 应该不会改变成员变量的值。在执行阶段改变委派类的成员变量的值 其实似乎在执行阶段改变这个流程。 那是线程不安全的,通常会导致与预期不符的结果。 作为一个异常,getter和setter都可以使用来注入配置, 因为它们在委派类实际使用在流程执行之前 就被调用了。

    10.3. 对象引用

    TODO

    10.4. 设计期间 对 运行期间

    TODO: 设计期间的活动行为应该和运行期间的行为相同。

    10.5. 用户代码拦截器

    TODO: 用户代码拦截器

    10.6. 成员变量配置 对 配置

    TODO: 记录成员变量配置 对 配置

 

第 11 章 环境

    11.1. 简介

    环境组件由连接环境组合是一种控制反转(IoC)容器。 它读取配置信息, 描述对象应该如何实例化,如何配置,如何连接在一起。

    环境被用来获得Activity实现 和流程虚拟机所需的资源和服务。 主要目的是让流程虚拟机的多个部分可配置, 这样PVM和运行在顶部的语言可以工作在一个标准Java环境 也可以在一个企业Java环境。

    环境被分成一系列环境。 每个环境可以拥有自己的生命周期。比如,流程引擎环境会 覆盖应用的全生命时间。块环境只对try-finally块周期内起作用。 一般一个块环境负责一个数据库事务。 每个环境暴露一个key-value列表。 pairs.

    11.2. EnvironmentFactory

    为了开始和一个环境进行工作,你需要一个EnvironmentFactory. 一个单独的环境工厂对象可以被用在整个应用的完整生命时间里。 所以一般这会保存在一个静态成员变量中。 EnvironmentFactory自己在流程引擎环境中

    一个EnvironmentFactory一般被获得 通过解析一个配置文件,像这样:

 

static EnvironmentFactory environmentFactory =
        EnvironmentFactory.parse(new ResourceStreamSource("pvm.cfg.xml");

 

    参考javadocs包org.jbpm.stream,获得流源的更多类型。

    这是一个环境工厂中的默认解析器, 会创建DefaultEnvironmentFactory. 这个主意是我们也会支持spring作为Ioc容器,但是这还处于TODO状态。 很欢迎大家在这方面帮助我们:-)。这个解析器可以使用静态setter方法配置 EnvironmentFactory.setParser(Parser)。

    11.3. 环境块

    一个环境为try-finally块的范围而存在。 这是一个环境块看起来像怎样:

 

Environment environment = environmentFactory.openEnvironment();
try {

  ...

} finally {
  environment.close();
}

 

    环境块定义了另一个生命部分:block环境。 一个事务可以一个定义在块环境中的对象的 常用例子。

    在这样一个块中,对象可以通过名称或类型在环境中查找到。 如果对象可以从环境中通过environment.get(String name) 或<T> T environment.get(Class<T>) 找到。

    当一个环境被创建时,它拥有一个process-engine环境 和一个block 环境。

    在默认实现中,process-engine环境和 block环境是WireContext. 一个WireContext包含对象如何创建和 如果绑定到正式的对象图中。

 

11.4. 实例

    为了启动简单实例,我们会需要一个Book:

 

public class Book {
  ...
  public Book() {}
  ...
}

 

    然后让我们创建一个环境工厂,知道如何创建book

 

static EnvironmentFactory environmentFactory = EnvironmentFactory.parseXmlString(
    "<jbpm-configuration>" +
    "  <process-engine>" +
    "    <object name='book' class='org.jbpm.examples.ch09.Book' />" +
    "  </process-engine>" +
    "</jbpm-configuration>"
));

 

    现在我们会创建一个环境块,使用这个环境工厂, 我们会查找环境中的book. 第一次会使用类型查找,第二次会使用名称查找。

 

Environment environment = environmentFactory.openEnvironment();
try {

  Book book = environment.get(Book.class);
  assertNotNull(book);

  assertSame(book, environment.get("book"));

} finally {
  environment.close();
}

 

    为了防止这样,你需要把环境作为一个参数传递给所有方法, 当前的环境被维护在一个threadlocal栈中:

 

Environment environment = Environment.getCurrent();

 

    11.5. 环境

    环境可以动态被添加和删除。 任何东西都可以暴露为一个Context.

 

public interface Context {

  Object get(String key);
  <T> T get(Class<T> type);
  Set<String> keys();

  ...
}

 

    当在环境中执行一次查找,这里有一个默认的搜索次序 哪些环境会为请求的对象检测。 默认次序与环境添加的顺序相反。 比如,如果一个对象被定义在流程引擎环境和块环境中, 块环境被认为更适用的,会被首先检测。 可选的,一个默认的搜索次序可以被传递给get 作为一个可选的参数。

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值