jbpm5.4应用1-资源加载分析
<jbpm:kbase id="kbase"><jbpm:resources><jbpm:resource type="BPMN2" source="classpath:HelloWorld.bpmn2"/></jbpm:resources></jbpm:kbase>
?这样如果资源特别多的时候,配置起来会很麻烦,那有没有一种方式可以配置制定一个目录呢,比如这样
<jbpm:kbase id="kbase"><jbpm:resources><jbpm:resource type="BPMN2" source="classpath:jbpm"/></jbpm:resources></jbpm:kbase>
?由于资料比较少,另外也想大概研究下其源码,于是跟踪了下,大体过程是这样的
1.spring加载其核心xml配置由根beans这个根元素开始加载,然后每个命名空间都有对应的解析类,比如jbpm这个命名空间对应的解析类是org.drools.container.spring.namespace.SpringDroolsHandler
具体怎么看这个解析类是什么呢,可以看下drools-spring的包下的META-INF下有两个文件
? spring.handlers:主要配置解析类,内容如下
http\://drools.org/schema/drools-spring=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.2.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.3.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.4.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.5.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.6.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.7.0=org.drools.container.spring.namespace.SpringDroolsHandler
?前边是命名空间,后边对应的是解析类
然后是另一个文件
spring.schemas:命名空间xsd与包中实际xsd的对应关系,主要用作xsd的校验
内容如下
http\://drools.org/schema/drools-spring=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.2.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.3.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.4.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.5.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.6.0=org.drools.container.spring.namespace.SpringDroolsHandlerhttp\://drools.org/schema/drools-spring-1.7.0=org.drools.container.spring.namespace.SpringDroolsHandler
2.解析类是如何处理的
来看下其源码
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class SpringDroolsHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser( "resource", new ResourceDefinitionParser() ); registerBeanDefinitionParser( "resource-change-scanner", new ResourceChangeScannerDefinitionParser() ); registerBeanDefinitionParser( "model", new ResourceDefinitionParser() ); registerBeanDefinitionParser( "kbase", new KnowledgeBaseDefinitionParser() ); registerBeanDefinitionParser( "kagent", new KnowledgeAgentDefinitionParser() ); registerBeanDefinitionParser( "kstore", new KnowledgeStoreDefinitionParser() ); registerBeanDefinitionParser( "ksession", new KnowledgeSessionDefinitionParser() ); registerBeanDefinitionParser( "grid", new GridDefinitionParser() ); registerBeanDefinitionParser( "grid-node", new GridNodeDefinitionParser() ); registerBeanDefinitionParser( "eventListeners", new EventListenersDefinitionParser() ); registerBeanDefinitionParser( "fileLogger", new KnowledgeLoggerDefinitionParser() ); registerBeanDefinitionParser( "consoleLogger", new KnowledgeLoggerDefinitionParser() ); registerBeanDefinitionParser( "environment", new EnvironmentDefinitionParser() ); }}
?可以看到该类只提供了一个init方法,并且注册了每个元素对应的具体的解析类,这里和资源有关的是
registerBeanDefinitionParser( "resource", new ResourceDefinitionParser() );registerBeanDefinitionParser( "kbase", new KnowledgeBaseDefinitionParser() );
?
?根据xml的关系会先执行kbase对应的解析类,其中和resource有关的代码如下
public static ManagedList getResources(Element element, ParserContext parserContext, BeanDefinitionBuilder factory) { Element resourcesElm = DomUtils.getChildElementByTagName( element, "resources" ); ManagedList resources = null; if ( resourcesElm != null ) { List<Element> childElements = DomUtils.getChildElementsByTagName( resourcesElm, "resource" ); if ( childElements != null && !childElements.isEmpty() ) { resources = new ManagedList(); for ( Element childResource : childElements ) { BeanDefinition resourceDefinition = parserContext.getDelegate().parseCustomElement( childResource, factory.getBeanDefinition() ); resources.add( resourceDefinition ); } } } return resources; }
?在执行parserContext.getDelegate().parseCustomElement的时候会调用resource对应的解析类,如下
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition( DroolsResourceAdapter.class ); if ( StringUtils.hasText( element.getAttribute( REF ) ) ) { String ref = element.getAttribute( REF ); emptyAttributeCheck( element.getLocalName(), REF, ref ); return (AbstractBeanDefinition) parserContext.getRegistry().getBeanDefinition( ref ); } String source = element.getAttribute( SOURCE_ATTRIBUTE ); emptyAttributeCheck( element.getLocalName(), SOURCE_ATTRIBUTE, source ); factory.addPropertyValue( "resource", source ); String type = element.getAttribute( TYPE_ATTRIBUTE ); String resourceType = type == null || type.length() == 0 ? ResourceType.DRL.getName() : type; factory.addPropertyValue( "resourceType", resourceType ); boolean basicAuthenticationEnabled = element.getAttribute( BASIC_AUTHENTICATION_ATTRIBUTE ) != null && element.getAttribute( BASIC_AUTHENTICATION_ATTRIBUTE ).equalsIgnoreCase( "enabled" ); factory.addPropertyValue( "basicAuthenticationEnabled", basicAuthenticationEnabled ); if ( basicAuthenticationEnabled ) { String username = element.getAttribute( USERNAME_ATTRIBUTE ); factory.addPropertyValue( "basicAuthenticationUsername", username ); String password = element.getAttribute( PASSWORD_ATTRIBUTE ); factory.addPropertyValue( "basicAuthenticationPassword", password ); } String name = element.getAttribute( NAME ); factory.addPropertyValue( "name", org.drools.core.util.StringUtils.isEmpty(name) ? null : name); String description = element.getAttribute( DESCRIPTION ); factory.addPropertyValue( "description", org.drools.core.util.StringUtils.isEmpty(description) ? null : description); if ( "xsd".equals( resourceType.toLowerCase() ) ) { XsdParser.parse( element, parserContext, factory ); } else if ( "dtable".equals( resourceType.toLowerCase() ) ) { List<Element> childElements = DomUtils.getChildElementsByTagName( element, "decisiontable-conf" ); if ( !childElements.isEmpty() ) { Element conf = childElements.get( 0 ); DecisionTableConfigurationImpl dtableConf = new DecisionTableConfigurationImpl(); String inputType = conf.getAttribute( INPUT_TYPE_ATTRIBUTE ); emptyAttributeCheck( conf.getLocalName(), INPUT_TYPE_ATTRIBUTE, inputType ); dtableConf.setInputType( DecisionTableInputType.valueOf( inputType ) ); String worksheetName = conf.getAttribute( WORKSHEET_NAME_ATTRIBUTE ); emptyAttributeCheck( conf.getLocalName(), WORKSHEET_NAME_ATTRIBUTE, worksheetName ); dtableConf.setWorksheetName( worksheetName ); factory.addPropertyValue( "resourceConfiguration", dtableConf ); } } return factory.getBeanDefinition(); }
?然后将其封装为DroolsResourceAdapter返回。
这部分主要是将xml转换为bean对象,基本也是按照xml配置的方式去读取dom元素,并没有提供类似查找路径下具体文件的行为。
下边是运行时的截图
?这个图片展现了解析的一个堆栈情况,其实具体解析还是调用spring的NamespaceHandlerSupport下的parse来进行处理的,而每个元素的解析是通过spring的AbstractBeanDefinitionParser来控制过程,然后每个子元素的实现来执行具体的功能。
?3.真正加载的时候
之前分析了解析xml的过程,但是并未见到加载资源的踪迹,那么下面看来一下这个过程。
既然resource是在kbase下,那么加载的过程也应该是在kbase中进行,来看一下kbase对应的bean factory的afterPropertiesSet方法
?
public void afterPropertiesSet() throws Exception { /*上边的代码先忽略 来看看资源加载的过程*/ for ( DroolsResourceAdapter res : resources ) { if ( res.getResourceType().equals( ResourceType.XSD ) ) { xsds.add( (JaxbConfigurationImpl) res.getResourceConfiguration() ); } if ( res.getResourceConfiguration() == null ) { kbuilder.add( res.getDroolsResource(), res.getResourceType() ); } else { kbuilder.add( res.getDroolsResource(), res.getResourceType(), res.getResourceConfiguration() ); } } KnowledgeBuilderErrors errors = kbuilder.getErrors(); if ( !errors.isEmpty() ) { throw new RuntimeException( errors.toString() ); } kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); KnowledgeBaseImpl kbaseImpl = (KnowledgeBaseImpl) kbase; kbaseImpl.jaxbClasses = new ArrayList<List<String>>(); for ( JaxbConfigurationImpl conf : xsds ) { kbaseImpl.jaxbClasses.add( conf.getClasses() ); } }?
?
?可以看到其实和官网给的例子一样,是用kbuilder来处理的,那么来跟踪一下这个处理类只有一个实现类org.drools.builder.impl.KnowledgeBuilderImpl
加载资源通过add方法
?
public void add(Resource resource, ResourceType type, ResourceConfiguration configuration) { pkgBuilder.registerBuildResource(resource); pkgBuilder.addKnowledgeResource( resource, type, configuration ); }?是通过pkgBuilder来实现的,对应类为org.drools.compiler.PackageBuilder
?
通过addKnowledgeResource方法来进行处理
?
public void addKnowledgeResource( Resource resource, ResourceType type, ResourceConfiguration configuration ) { try { ( (InternalResource) resource ).setResourceType( type ); if (ResourceType.DRL.equals( type )) { addPackageFromDrl( resource ); } else if (ResourceType.DESCR.equals( type )) { addPackageFromDrl( resource ); } else if (ResourceType.DSLR.equals( type )) { addPackageFromDslr( resource ); } else if (ResourceType.DSL.equals( type )) { addDsl( resource ); } else if (ResourceType.XDRL.equals( type )) { addPackageFromXml( resource ); } else if (ResourceType.BRL.equals( type )) { addPackageFromBrl( resource ); } else if (ResourceType.DRF.equals( type )) { addProcessFromXml( resource ); } else if (ResourceType.BPMN2.equals( type )) { BPMN2ProcessFactory.configurePackageBuilder( this ); addProcessFromXml( resource ); } else if (ResourceType.DTABLE.equals( type )) { addPackageFromDecisionTable( resource, configuration ); } else if (ResourceType.PKG.equals( type )) { addPackageFromInputStream(resource); } else if (ResourceType.CHANGE_SET.equals( type )) { addPackageFromChangeSet(resource); } else if (ResourceType.XSD.equals( type )) { addPackageFromXSD(resource, (JaxbConfigurationImpl) configuration); } else if (ResourceType.PMML.equals( type )) { addPackageFromPMML(resource, type, configuration); } else { addPackageForExternalType(resource, type, configuration); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException( e ); } }?在分析了具体的处理类之后,发现当type为CHANGE_SET的时候对应的处理方法为addPackageFromChangeSet
?
?
void addPackageFromChangeSet(Resource resource) throws SAXException, IOException { XmlChangeSetReader reader = new XmlChangeSetReader( this.configuration.getSemanticModules() ); if (resource instanceof ClassPathResource) { reader.setClassLoader( ( (ClassPathResource) resource ).getClassLoader(), ( (ClassPathResource) resource ).getClazz() ); } else { reader.setClassLoader( this.configuration.getClassLoader(), null ); } Reader resourceReader = null; try { resourceReader = resource.getReader(); ChangeSet changeSet = reader.read( resourceReader ); if (changeSet == null) { // @TODO should log an error } for (Resource nestedResource : changeSet.getResourcesAdded()) { InternalResource iNestedResourceResource = (InternalResource) nestedResource; //注意看这里判断是否为目录 然后循环目录中的文件 if (iNestedResourceResource.isDirectory()) { for (Resource childResource : iNestedResourceResource.listResources()) { //这里的代码显示并不支持2级目录 if (( (InternalResource) childResource ).isDirectory()) { continue; // ignore sub directories } ( (InternalResource) childResource ).setResourceType( iNestedResourceResource.getResourceType() ); addKnowledgeResource( childResource, iNestedResourceResource.getResourceType(), iNestedResourceResource.getConfiguration() ); } } else { addKnowledgeResource( iNestedResourceResource, iNestedResourceResource.getResourceType(), iNestedResourceResource.getConfiguration() ); } } } finally { if (resourceReader != null) { resourceReader.close(); } } }?以上代码的大体过程是解析type为CHANGE_SET的资源文件(xml文件,具体格式见下面),然后找到配置的具体文件或者路径,判断是否为目录,如果是目录的话加载目录下的文件,但并不支持目录下的目录,然后将对应的文件加载。
?
修改后的xml如下
spring配置文件
?
<jbpm:kbase id="kbase"><jbpm:resources><jbpm:resource type="CHANGE_SET" source="classpath:bpmnChangeSet.xml"/></jbpm:resources></jbpm:kbase>?bpmnChangeSet.xml<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set change-set-1.0.0.xsd'> <add> <resource source="classpath:bpmn" type="BPMN2"/> </add></change-set>?这样就可以将所有的资源文件指定到具体的目录了。
其实如果细心的话,在kbuilder的接口里有文档说明了这个情况,感兴趣的朋友可以去看看
?
?