【jbpm4.4源码阅读笔记】engine的解析与生成

  jpbm4.4源码的包结构主要有七个,分为org.jbpm.api;org.jbpm.bpmn;org.jbpm.enterprise.internal;org.jbpm.internal.log;org.jbpm.jpdl.internal;pvm.internal; 简而言之,api为接口,比如service、dao等的接口,bpmn定义了jbpm模型,比如task、end等节点的属性和动作,pvm即工作流虚拟机,是jbpm的核心实现;jpdl则是java process define language。多数牛人写的开源代码非常复杂,也很庞大,比如我们常用的spring等。仅就jbpm而言,代码量属于少量级的,但如果从头至尾全面、深入的看完,对于我等菜鸟、初级程序员而言,也是一件棘手且庞大的活动。我们学习框架或流程引擎的源码,本意也并不是记忆背诵或者是全盘理解,我们更希望通过源码学习其中的设计思想、编码风格以及与我们在使用jbpm中经常用到的功能的相关部分的实现思路或解决方案,更希望得到更多的原理性、技巧性以及通用性的知识用以指导我们以后的工作,同时关注代码的风格、设计模式以及使用的Java高级技术,做到触类旁通。

  在阅读源码的时候,我的建议不是从第一个源代码依次读起,而应该选择一个入口。通过这个入口一步步进行;或者通过我们在应用中所引用的功能接口,逐步深入,相互映射关联,最终形成一张相对完整的知识网络。

  使用jbpm的都知道,jbpm的核心是流程引擎,流程引擎通过配置文件定义。我们一般通过以下方式得到流程引擎:

ProcessEngine processEngine = Configuration.getProcessEngine();

 我们按照这个主线进行下去,看看jbpm如何通过jbpm配置文件得到processEngine。通过Configuration,按F4得到其类结构,以及getProcessEngine的实现,可以看出这是设计模式中最简单的一种即单例模式。关于单例模式的原理以及实现单例模式的常见三种形式等等会以后的章节即jbpm与设计模式中详述。以下我们只简单看一下代码:

		org.jbpm.api.Configuration
		/** get the singleton ProcessEngine that is created from the default
		   * configuration file ‘jbpm.cfg.xml‘. */
		  public static ProcessEngine getProcessEngine() {
		    if (singleton == null) {
		      synchronized (Configuration.class) {
		        if (singleton == null) {
		          singleton = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();
		        }
		      }
		    }
		    return Configuration.singleton;
		  }

  继续跟踪代码,进入buildProcessEngine的实现中。如果没有使用JNDI或集成spring,直接进入instantiateProcessEngine方法:

@Override
	public ProcessEngine buildProcessEngine() {
		if (!isConfigured) {
			setResource(DEFAULT_CONFIG_RESOURCENAME);
		}
		if (jndiName != null) {
			try {
				InitialContext initialContext = new InitialContext();
				ProcessEngineImpl existing = (ProcessEngineImpl) initialContext
						.lookup(jndiName);
				if (existing != null) {
					log.debug("found existing process engine under " + jndiName);
					return existing;
				}
			} catch (NamingException e) {
				log.debug("jndi name " + jndiName + " is not bound");
			}
		}

		if (isSpringEnabled) {
			return SpringProcessEngine.create(this);
		}
		return instantiateProcessEngine();
	}

  

  进入instantiateProcessEngine方法,直接调用ProcessEngineImpl含参构造方法,public ProcessEngineImpl(ConfigurationImpl configuration),该方法有两个动作,一个是利用configuration初始化流程引擎,另外一个动作是校验DB。在这两个方法里不做更详细的梳理。但在initializeProcessEngine方法中有如下语句:

userCommandService = (CommandService) processEngineWireContext.get(CommandService.NAME_TX_REQUIRED_COMMAND_SERVICE);

  熟悉设计模式的都知道有一种设计模式叫命令模式,只看这一句的命名风格,我们就可以看出来这里应该与命令模式有关。

通过该方法是为了得到CommandService,我们打开看看,在该接口org.jbpm.pvm.internal.cmd.CommandService的注释上有如下信息:

/**
	 * abstract extensible session facade.  Developers can use this directly or 
	 * extend one of the implementations with custom methods.
	 * Developers should be encouraged to use this interface as it will 
	 * be kept more stable then direct usage of the API (which is still 
	 * allowed).
	 * All the method implementations should be based on commands.
	 * Each of the method implementations will have a environment block.
	 * Then the command is executed and the environment is passed into the 
	 * command.
	 */

  以上注释翻译过来即是命令模式含义及其应用场景:

抽象的可扩展session外观定义。开发者可以直接使用或者继续一个包含自定义方法的实现类。因为该接口的健壮性,开发者应当鼓励实现这个接口,而不是直接使用这个API。所有的方法实现基于这个command。每一实现都将有一个上下文结构,上下文被传递入命令然后该自定义命令被执行。实现了命令调用与命令执行的分离。

  同单例模式一样,这里也不展开,在以后的jbpm与设计模式里再做详解。

  打开CommandService的类结构如下图:

  在最后我们看到了熟悉的字眼,Interceptor,使用struts2的都知道Struts的核心实现机制就是拦截器Interceptor,而拦截器是使用责任链模式的,这里对责任链也一笔带过。

  再返回到我们的ProcessEngineImpl中,看一下jbpm配置文件的解析过程,解析调用结构如下图,限于篇幅,不再按着调用结构一一查看,只整体看一下调用关系。

  我在调试的时候,发现在parseDocument的importDocument中已经将jbpm.cfg.xml中的元素解析出来,再往上看解析方法,importElement来自于documentElement,而documentElement是document的属性,而

parse.document = buildDocument(parse);

  可见buildDocument是解析jbpm.cfg.xml的主要过程,调试过程中的抓图:

  该方法使用xml的SAX解析:

protected Document buildDocument(Parse parse) {
	    DocumentBuilder documentBuilder = createDocumentBuilder(parse);
	    InputSource inputSource = parse.getInputSource();
	    try {
	      return documentBuilder.parse(inputSource);
	    }
	    catch (IOException e) {
	      parse.addProblem("could not read input", e);
	    }
	    catch (SAXException e) {
	      parse.addProblem("failed to parse xml", e);
	    }
	    return null;
	  }

  Parse方法则直接调用SAX,最后生成Document,此处不详述。

继续回到ConfigurationParser中的parseDocument方法中,其中L57有一句

ConfigurationImpl configuration = parse.contextStackFind(ConfigurationImpl.class);

其实现过程,可以看出这是一处反射:

	public <T> T contextStackFind(Class<T> clazz) {
	    if ( (contextStack!=null)
	         && (! contextStack.isEmpty())
	       ) {
	      ListIterator<Object> listIter = contextStack.listIterator(contextStack.size());
	      while (listIter.hasPrevious()) {
	        Object object = listIter.previous();
	        if (clazz.isInstance(object)) {
	          return clazz.cast(object);
	        }
	      }
	    }
	    return null;
	  }

再回到ConfigurationParser中

Element documentElement = document.getDocumentElement();
    if (documentElement != null) {
      // this code will be called for the original jbpm.cfg.xml document as 
      // well as for the imported documents.  only one of those can specify
      // a spring-cfg.  for sure no 2 config files can specify different jndi-names
      String spring = XmlUtil.attribute(documentElement, "spring");
      if ("enabled".equals(spring)) {
        configuration.springEnabled();
      }
  
      // this code will be called for the original jbpm.cfg.xml document as 
      // well as for the imported documents.  only one of those can specify
      // a jndi-name.  for sure no 2 config files can specify different jndi-names
      String jndiName = XmlUtil.attribute(documentElement, "jndi-name");
      if (jndiName!=null) {
        if ( (configuration.getJndiName()!=null)
             && (!jndiName.equals(configuration.getJndiName()))
           ) {
          parse.addProblem("duplicate jndi name specification: "+jndiName+" != "+configuration.getJndiName());
        } else {
          configuration.jndi(jndiName);
        }
      }
  
      for (Element importElement : XmlUtil.elements(documentElement, "import")) {
        if (importElement.hasAttribute("resource")) {
          String resource = importElement.getAttribute("resource");
          Parse importParse = createParse()
            .setResource(resource)
            .contextStackPush(configuration)
            .propagateContexMap(parse)
            .execute();
          
          parse.addProblems(importParse.getProblems());
        }
      }
  
      Element processEngineElement = XmlUtil.element(documentElement, "process-engine-context");
      if (processEngineElement != null) {
        WireDefinition processEngineContextDefinition = configuration.getProcessEngineWireContext().getWireDefinition();
        parse.contextStackPush(processEngineContextDefinition);
        try {
          processEngineContextParser.parseDocumentElement(processEngineElement, parse);
        } finally {
          parse.contextStackPop();
        }
      }
  
      Element txCtxElement = XmlUtil.element(documentElement, "transaction-context");
      if (txCtxElement != null) {
        WireDefinition transactionContextDefinition = configuration.getTransactionWireDefinition();
        parse.contextStackPush(transactionContextDefinition);
        try {
          transactionContextParser.parseDocumentElement(txCtxElement, parse);
        } finally {
          parse.contextStackPop();
        }
      }
    }

    parse.setDocumentObject(configuration);

  在这个解析方法里,有import、process-engine-context、transaction-context。这几个词是什么意思呢,肯定与xml文件有关。jbpm的配置文件有很多:

  我们随便打开一个,我们先看一下主配置文件,即jbpm.cfg.xml

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

<jbpm-configuration>

  <import resource="jbpm.default.scriptmanager.xml" />
  <import resource="jbpm.mail.templates.xml" />

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

    <object class="org.jbpm.pvm.internal.id.DatabaseDbidGenerator">
      <field name="commandService"><ref object="newTxRequiredCommandService" /></field>
    </object>

    <object class="org.jbpm.pvm.internal.id.DatabaseIdComposer" init="eager" />
    
    <object class="org.jbpm.pvm.internal.el.JbpmElFactoryImpl" />

    <types resource="jbpm.variable.types.xml" />

    <address-resolver />
 
  </process-engine-context>

  <transaction-context>
    <repository-session />
    <db-session />
    
    <message-session />
    <timer-session />
    
    <history-sessions>
      <object class="org.jbpm.pvm.internal.history.HistorySessionImpl" />
    </history-sessions>
    
    <mail-session>
      <mail-server>
        <session-properties resource="jbpm.mail.properties" />
      </mail-server>
    </mail-session>

  </transaction-context>

</jbpm-configuration>

  代码中的三个词其实是配置文件中的节点名称。我们拿出一个节点的解析:

Element processEngineElement = XmlUtil.element(documentElement, "process-engine-context");
      if (processEngineElement != null) {
        WireDefinition processEngineContextDefinition = configuration.getProcessEngineWireContext().getWireDefinition();
        parse.contextStackPush(processEngineContextDefinition);
        try {
          processEngineContextParser.parseDocumentElement(processEngineElement, parse);
        } finally {
          parse.contextStackPop();
        }
      }

  这个解析是一个递归的过程,在本方法里有又调用方法自身。

  代码以set Configuration结尾:

parse.setDocumentObject(configuration);

自此,解析过程完全结束,我们从configuration中得到引擎。

源码的阅读宜粗不宜细,抓住主线,以点到面,形成相对完整的知识网络,所以关于引擎解析及生成的源码阅读到此结束。 

时间: 2024-10-11 03:30:28

【jbpm4.4源码阅读笔记】engine的解析与生成的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

Apache Storm源码阅读笔记

欢迎转载,转载请注明出处. 楔子 自从建了Spark交流的QQ群之后,热情加入的同学不少,大家不仅对Spark很热衷对于Storm也是充满好奇.大家都提到一个问题就是有关storm内部实现机理的资料比较少,理解起来非常费劲. 尽管自己也陆续对storm的源码走读发表了一些博文,当时写的时候比较匆忙,有时候衔接的不是太好,此番做了一些整理,主要是针对TridentTopology部分,修改过的内容采用pdf格式发布,方便打印. 文章中有些内容的理解得益于徐明明和fxjwind两位的指点,非常感谢.

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

Yii源码阅读笔记 - 日志组件

?使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category);Yii::trace($message, $category); 两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG: defined('YII_DEBUG') or define('YII_DEBUG', true); Yii::log方法的调用需要指定message的level和category.category是格式为“xxx.yyy.z