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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

 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的核心是流程引擎,流程引擎通过配置文件定义。我们一般通过以下方式得到流程引擎:

1
ProcessEngine processEngine = Configuration.getProcessEngine();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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方法中有如下语句:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
/**
      * 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的类结构如下图:

wKiom1UELJKQ1lYcAADiblo09o4158.jpg

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

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

wKiom1UELW2xodiTAAbgLpFyoig987.bmp

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

1
parse.document = buildDocument(parse);

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

wKiom1UELofw9wAJAAfV0owjwqA987.bmp

  该方法使用xmlSAX解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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有一句

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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的配置文件有很多:

wKioL1UENLTS2IZUAAX88tAL26I106.bmp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<? 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 >

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

1
2
3
4
5
6
7
8
9
10
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结尾:

1
parse.setDocumentObject(configuration);

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

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




     本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1620413,如需转载请自行联系原作者



相关文章
|
28天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
63 0
|
28天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
50 0
|
28天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
58 0
|
28天前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
74 0
|
9天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
28 3
|
26天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
51 5
|
28天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
28天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
29天前
|
存储 Java API
从源码角度解析ArrayList.subList的几个坑!
从源码角度解析ArrayList.subList的几个坑!
|
1月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
51 2

推荐镜像

更多
下一篇
无影云桌面