Jbpm配置代码分析
作者:吴大愚
Email:dywu_xa@sina.com
2006-11-2
版本 1.0
适用于jbpm3.1版本
4. ObjectInfo是如何创建对象的
在基本分析清楚ObjectFactory之后,我们再进一步,来看看ObjectInfo是如何创建对象的。并且具体研究一下BeanInfo和JbpmContextInfo的机制。当对JbpmContextInfo分析清楚之后,我们就离搞清楚Jbpm如何使用管理功能模块只差一步了:)
在这一部分,Jbpm大量使用了Java的反射机制,希望同时能对学习java反射机制的朋友有所帮助。
4.1. Interface ObjectInfo
org.jbpm.configuration.ObjectInfo 其定义如下:
public interface ObjectInfo extends Serializable {
boolean hasName();
String getName();
boolean isSingleton();
Object createObject(ObjectFactoryImpl objectFactory);
}
在下图中显示出和这个接口相关的类。
图 六
实现ObjectInfo接口的类是用来创建对应类的实例的。比如一个StringInfo对象,通过它的createObject方法就能够创建出一个String对象。一个BeanInfo对象,通过它的createObject方法就能够创建出一个对象,而这个对象是什么类型的,和BeanInfo对象内部存储的信息有关。例如BeanInfo内部存储的HashMap类的信息,那么它创建出来的对象就是HashMap对象。Jbpm在BeanInfo类的实现上使用了大量java反射机制。一个JbpmContextInfo对象,能够创建出JbpmContext对象。实际上,JbpmConfiguration.createJbpmContext()方法,最终是由保存在JbpmConfiguration.ObjectFactory.nameObjectInfos里面的JbpmContextInfo对象的createObject方法创建出来的。
Jbpm.cfg.xml配置文件中,每一个节点中的信息就是创建一个ObjectInfo对象的信息。即,配置文件中每一个节点就对应一个ObjectInfo对象,节点的标签名称,就决定ObjectInfo对象的类型。例如<jbpmContext>,<string>,<bean>对应的就为JbpmContextInfo,StringInfo,BeanInfo类的实例。而节点的name属性,也就对应于ObjectInfo对象的name成员。可以通过getName()和hasName()这个两个方法得到和判断name属性。节点的singlteon属性对应于ObjectInfo对象的isSinglteon成员,可以通过isSinglteon()方法得到该属性。
4.2. Class AbstractObjectInfo
我们首先来分析一下AbstractObjectInfo类。从上图可以看出,AbstractObjectInfo类是所有实现ObjectInfo接口的实际类的父类。
AbstractObjectInfo类有两个成员,分别为:
String name;
boolean isSingleton;
这两个成员自然对应于ObjectInfo接口中的前三个操作。
构造函数public AbstractObjectInfo(Element element, ObjectFactoryParser objectFactoryParser)包含两个参数。第一个是xml解析得到的Element元素。例如在配置文*件中<string name = "resource.business.calendar" value = "org/jbpm/calendar/jbpm.business.calendar.properties"/>这样一个节点就对应一个Element对象。第二个参数是一个ObjectFactoryParser对象。我们知道ObjectFactoryParser对象需要现准备好参数,然后根据参数创建ObjectFactoryImpl对象。而这个要准备的参数就是一个Map对象nameObjectInfos,需要将创建配置文件中所有节点对应的ObjectInfo对象都放进去。怎么放进去呢?这就是第二个参数的作用。通过在构造函数中调用objectFactoryParser.addNamedObjectInfo(name, this)来完成这个步骤。
方法protected String getValueString(Element element)得到的就是配置文件中节点的value属性所对应的字符串。
4.3. Class StringInfo
StringInfo类很简单,所以适合先来分析:)
例如:<string name = "resource.business.calendar" value = "org/jbpm/calendar/jbpm.business.calendar.properties"/>配置文件中这样一个节点,对应创建的StringInfo对象,这个对象的getName()返回"resource.business.calendar"。createObject()返回一个String对象,值为"org/jbpm/calendar/jbpm.business.calendar.properties"。
4.4. Class BeanInfo
BeanInfo类和类似于StringInfo这样的简单类型的ObjectInfo类不太一样。像StringInfo这样的类,它创建出来的对象的类型是确定的。但BeanInfo类不是这样的,它是使用反射机制,能够创建出来不同类型的对象。
例如:<bean name = "jbpm.task.instance.factory" class = "org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl" singleton="true" />配置文件中这样一个节点,对应的会创建一个BeanInfo对象,这个对象getName()返回"jbpm.task.instance.factory",isSingleton()返回true。而creatObject()创建的是一个org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl类的实例。
创建一个对象,并不是简单的在createObject()方法中把它new出来就可以了。这里需要一点反射机制。通过反射机制加载类,主要有两种方式。
第一种是通过ClassLoader直接加载这个类的名称,得到类的Class对象,然后通过Class对象的newInstance()方法创建出对象来,这种方法只适用于有参数为空的构造函数的类;
第二种是在有要加载类的构造函数的描述信息的情况下,先创建构造函数参数的对象,然后通过类的Class对象的getDeclaredConstructor方法得到描述类的Constructor对象,通过Constructor对象的newInstance方法,并加入前面创建好的参数对象,就可以创建类的实例。 这种方法适用于加载构造函数需要参数的类。
在BeanInfo类中有三个成员,分别是:
String className ;
ConstructorInfo constructorInfo;
PropertyInfo[] propertyInfos;
className用来存放要创建类的完整类名。如果配置结点包括class属性(我们看到的默认配置文件中都是这样做的),那么在创建BeanInfo对象的时候就会设置这个属性。调用createObject方法时,如果没有构造函数描述信息的话,就会使用这个className信息来创建对象。其主要代码如下:
Class clazz = objectFactory.loadClass(className);
object = clazz.newInstance();
在BeanInfo中,还是用了另外两个类,分别是ConstructorInfo类和PropertyInfo类。这两个类是用来存储要构建类的构造函数信息和属性信息。在默认的配置文件中,还没有相关的节点可以作为例子。在src/java.jbpm.test目录下的org.jbpm.configuration .BeanFactoryTest类里面,有很多例子,大家可以在这个类里面看到如何书写BeanInfo的配置信息。
现举一例,例如:
"<beans>" +
" <bean class='org.jbpm.configuration.BeanFactoryTest$MyBean'>" +
" <constructor>" +
" <parameter class='java.lang.String'>" +
" <string>hello</string>" +
" </parameter>" +
" <parameter class='java.lang.Integer'>" +
" <integer>6</integer>" +
" </parameter>" +
" </constructor>" +
" <field name='address'><string>nudt</string></field>" +
" <field name='count'><integer>10</integer></field>" +
" </bean>" +
"</beans>"
这描述了BeanInfo对象在创建org.jbpm.configuration.BeanFactoryTest$MyBean对象的时候,会使用它的构造函数。这个构造函数包括两个参数,一个是String类型的”hello”,另外一个是整型的6。此外这个类在构造完后,还会向类的两个属性address和count中,分别注入”nudt”和10两个值。最后再返回这个创建好的对象。
如果配置文件是如此描述的,那么相应的BeanInfo对象,在它的构造函数中就会去创建它的ConstructorInfo对象,用来保存构造函数信息。还会去创建PropertyInfo对象数组,用来保存属性列表信息。在createObject()方法中就会去使用这两个成员。
有关ConstructorInfo和PropertyInfo的分析就不再具体展开,有兴趣的朋友可以自己去研究,如有问题可以和我联系。
具体的创建时序图如下:
图 七
4.5. Class JbpmContextInfo
好了,该我们的主角上场了。
JbpmContextInfo类有五个成员,和两个方法。我们需要的就是搞清楚这五个成员的用途,还有就是它的createObject方法。
成员如下:
Map serviceFactoryObjectInfos;
Map serviceFactories;
List serviceNames ;
ObjectInfo[] saveOperationObjectInfos;
List saveOperations;
4.5.1. 创建JbpmContextInfo对象
在配置文件中<jbpm-context>节点下有多个<service>子节点。这些节点描述的都是不同服务的构造工厂类和名称。例如:<service name = "persistence" factory = "org.jbpm.persistence.db.DbPersistenceServiceFactory" />
在JbpmContextInfo的构造函数中,会首先解析这个<service>列表。对于每一个service创建一个BeanInfo对象,并且使用结点的factory属性来设置BeanInfo对象的className属性为。然后以节点name属性,和相应的BeanInfo对象为对,添加到成员Map serviceFactoryObjectInfos中。同时在serviceNames这个List中保存所有的service的name属性。
换句话说,也就是在创建JbpmContextInfo的时候,将所有配置文件中的serivce节点都构造成为BeanInfo对象,和相对应的name属性一起存在了JbpmContextInfo的serviceFactoryObjectInfos成员和serviceNames成员中。
在默认的配置文件中,有一种结点没有出现过,那就是<save-operations>。这种节点可以包含多个<save-operation>子节点。每个<save-operation>子节点像<service>子节点一样,也会创建一个BeanInfo对象。在JbpmContextInfo的构造函数中,还把由<save-operation>子节点得到的BeanInfo都存在了成员ObjectInfo[] saveOperationObjectInfos数组中。至于这些<save-operation>子节点是作甚么用处的,在文章的下一节,分析Service接口里面会详细提到。
4.5.2. 创建JbpmContext对象
要创建一个JbpmContext对象,需要一个类型为org.jbpm.svc.Services的参数。而要创建一个Services的对象,需要由三个参数。构造函数为:
public Services( Map serviceFactories, List serviceNames, List saveOperations)。
Services类我会在下一节详细分析,这里大家要知道的是在JbpmContext类中,所有的功能操作模块的对象都是通过一个Services对象来管理的。也就是说所有的操作模块都存在Services对象中。这也就是为什么要创建JbpmContext对象要先创建一个Services对象。
简单的说,Services类是用来创建,管理各种Service对象的。它构造函数中的参数serviceFactories就是它所能创建的Service对象的工厂类的集合。参数serviceNames是这些服务的名称列表。saveOperations是保存类的列表。
在JbpmContextInfo类的createObject()方法中,通过遍历serviceFactoryObjectInfos和saveOperationObjectInfos两个集合。调用每一个ObjectInfo对象(都是BeanInfo对象)的createObject方法。分别生成相应的对象集合,存放在serviceFactories和saveOperations两个成员中。再加上已有的serviceNames成员,用这三个成员作为参数,生成Services对象:
Services services = new Services(serviceFactories, serviceNames, saveOperations);
然后调用下面的语句:
return new JbpmContext(services, objectFactory);
到这里,很兴奋是吧。我们的明星终于出场了!而且这个明星装备完善,各种功能模块全部在手,真是想用谁就用谁:)
那么有哪些功能模块可以用呢?这些功能模块是怎么实现和怎么组织的呢?他们的结构是什么?JbpmContext对象又是如何使用的他们的呢?在下一节我再慢慢道来。