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