activiti 5.21工做流規則引擎擴展(businessRuleTask)

背景介紹:java

公司有本身的規則引擎配置平臺,執行核心爲drools,配置後生成規則腳本,存入數據庫,執行的時候調用drools的Api,關鍵代碼:
        StatelessSession statelessSession = ruleBase.newStatelessSession();
        statelessSession.setGlobal("externalConditionResult", true);
        statelessSession.execute(list);數據庫

之前一直是在後臺硬編碼調用每個規則。緩存

使用activiti5工做流引擎,節點使用上只是用了用戶任務和自動任務,封裝在兩個方面:1.流程啓動,任務獲取,完成任務,也就是對這幾個API的封裝,同時引入了流程業務關聯表。2。在流程節點分配這塊使用咱們系統的崗位或者角色,擁有該崗位/角色的人均可以處理這個任務,分配到崗位/角色這塊是經過規則配置的,後臺也是硬編碼的,如今平臺升級了之後,有這麼個需求,就是流程編輯器上能夠直接配置規則節點,這樣的話,用戶能夠直接修改流程圖,選擇要調用的規則session

activiti5自己是支持規則節點的,網上查一查,也能查到一些資料,總結一下:less

  1. 引入流程規則執行所需JAR包:
  2. 抒寫規則腳本,在頁面上部署工做流的時候,同時選擇要部署的規則腳本,添加到工做的act_ge_bytearray表,
  3. 配置流程配置文件:

<property name="customPostDeployers">
<list>
<bean class="org.activiti.engine.impl.rules.RulesDeployer" />
</list>
</property>編輯器

就是說執行到規則節點的時候,以.drl結尾的drool腳本會交給RulesDeploer部署,加載到內存,供規則節點執行器執行。ui

4。在流程編輯器上的規則節點添加規則名,輸入變量,輸出變量,繼承JavaDelegate獲取輸出變量,或者直接在流程圖上根據流程變量zhi配置節點的走向編碼

這樣看就完活了,可是實際執行咱們的規則的時候怎麼調試到報錯,這方面沒有完善的文檔,只能看源碼了只能看源代碼了。debug

首先咱們的規則執行方式是無狀態的StatelessKnowledgeSession,結果變量就是一個實體類,是以在內存中更新全局變量的方式返回的。調試

規則節點的執行是BusinessRuleTaskActivityBehavior來處理的

 KnowledgeBase knowledgeBase =RulesHelper.findKnowledgeBaseByDeploymentId(deploymentId); 
 StatefulKnowledgeSession ksession = knowledgeBase.newStatefulKnowledgeSession();

1.經過流程實例部署ID,在內存中找到以.drl結尾的腳本名稱,若是沒有,從新查詢act_ge_bytearray表,加載到內存,添加到知識庫.

2.獲取規則節點上配置的輸入輸出變量,規則名,若是不配置規則名,則所有執行,執行方式是經過有狀態StatefulKnowledgeSession的fireAllRules來執行規則腳本,其中一個關鍵點是咱們的規則腳本里面不少個規則名,總不能一個一個寫到編輯器上面吧,重寫執行源碼,發現工做流實現了drools的規則匹配,只需修改咱們的規則名已相同的名字結尾,流程編輯器上配置這個相同的名字,就能夠所有執行了,對於結果變量,drools不支持在他的工組內存中更新全局變量,若是須要這樣作,就要調用ksession.setGlobal("ruleResultBase", obj);將全局變量set到他的工做內存,獲取結果變量是一個集合,規則那一套基本不會改動,那怎麼辦,直接修改改這個類源碼好了,修改成獲取當個實體(不是一個好方法,後面會說到),就這樣把源碼拿出來改了,能夠部署了,調試也經過了。。。。,

第一個問題解決了:經過改動BusinessRuleTaskActivityBehavior源碼,工做流能夠調用咱們的規則腳本,能夠和規則聯動了。

public void execute(ActivityExecution execution) throws Exception {
    PvmProcessDefinition processDefinition = execution.getActivity().getProcessDefinition();
    String deploymentId = processDefinition.getDeploymentId();
    
    KnowledgeBase knowledgeBase = RulesHelper.findKnowledgeBaseByDeploymentId(deploymentId); 
    StatefulKnowledgeSession ksession = knowledgeBase.newStatefulKnowledgeSession();


    if (variablesInputExpressions != null) {
      Iterator<Expression> itVariable = variablesInputExpressions.iterator();
      while (itVariable.hasNext()) {
        Expression variable = itVariable.next();
        Object obj=variable.getValue(execution);
        if(obj instanceof IRuleResultBase){
            //設置全局變量
        	ksession.setGlobal("ruleResultBase", obj);
        }
        ksession.insert(variable.getValue(execution));
      }
    }

    if (!rulesExpressions.isEmpty()) {
      RulesAgendaFilter filter = new RulesAgendaFilter();
      Iterator<Expression> itRuleNames = rulesExpressions.iterator();
      while (itRuleNames.hasNext()) {
        Expression ruleName = itRuleNames.next();
        filter.addSuffic(ruleName.getValue(execution).toString());
      }
      filter.setAccept(!exclude);
      ksession.fireAllRules(filter);
      
    } else {
      ksession.fireAllRules();
    }
    //客戶化 結果bean
   /* Collection<Object> ruleOutputObjects = ksession.getObjects();
    if (ruleOutputObjects != null && !ruleOutputObjects.isEmpty()) {
      Collection<Object> outputVariables = new ArrayList<Object>();
      for (Object object : ruleOutputObjects) {
        outputVariables.add(object);
      }
      execution.setVariable(resultVariable, outputVariables);
    }*/
   
    /*ruleResultBase=(RuleResultBase) ksession.getGlobal("ruleResultBase");
    Collection<Object> outputVariables = new ArrayList<Object>();
    outputVariables.add(ruleResultBase);
    execution.setVariable(resultVariable, ruleResultBase);*/
    execution.setVariable(resultVariable, ksession.getGlobal("ruleResultBase"));
    ksession.dispose();
    leave(execution);
  }

 

可是有幾個問題,規則編輯以後難道要從新部署流程嗎?看流程是如何加載規則的

public class RulesHelper {

  public static KnowledgeBase findKnowledgeBaseByDeploymentId(String deploymentId) {
    DeploymentCache<Object> knowledgeBaseCache = Context
      .getProcessEngineConfiguration()
      .getDeploymentManager()
      .getKnowledgeBaseCache();
  
    KnowledgeBase knowledgeBase = (KnowledgeBase) knowledgeBaseCache.get(deploymentId);
    if (knowledgeBase==null) {
      DeploymentEntity deployment = Context
        .getCommandContext()
        .getDeploymentEntityManager()
        .findDeploymentById(deploymentId);
      if (deployment==null) {
        throw new ActivitiObjectNotFoundException("no deployment with id "+deploymentId, Deployment.class);
      }
      Context
        .getProcessEngineConfiguration()
        .getDeploymentManager()
        .deploy(deployment);
      knowledgeBase = (KnowledgeBase) knowledgeBaseCache.get(deploymentId);
      if (knowledgeBase==null) {
        throw new ActivitiException("deployment "+deploymentId+" doesn't contain any rules");
      }
    }
    return knowledgeBase;
  }
}

發現上面代碼從緩存中取KnowledgeBase, if (knowledgeBase==null),那麼根據部署id,去數據庫查詢,而後交給Deployer來部署,這是一個接口:

public interface Deployer {

  void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings);
}

對於規則的部署,activiti的實現類爲前面提到的,配置文件中配置的RulesDeployer

public class RulesDeployer implements Deployer {
  
  private static final Logger log = LoggerFactory.getLogger(RulesDeployer.class);

  public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
    log.debug("Processing deployment {}", deployment.getName());
    
    KnowledgeBuilder knowledgeBuilder = null;

    DeploymentManager deploymentManager = Context
      .getProcessEngineConfiguration()
      .getDeploymentManager();
    
    Map<String, ResourceEntity> resources = deployment.getResources();
    for (String resourceName : resources.keySet()) {
      log.info("Processing resource {}", resourceName);
      if (resourceName.endsWith(".drl")) { // is only parsing .drls sufficient? what about other rule dsl's? (@see ResourceType)
        if (knowledgeBuilder==null) {
          knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        }
     
        ResourceEntity resourceEntity = resources.get(resourceName);
        byte[] resourceBytes = resourceEntity.getBytes();
        Resource droolsResource = ResourceFactory.newByteArrayResource(resourceBytes);
       
        knowledgeBuilder.add(droolsResource, ResourceType.DRL);
        
      }
    }
    
    if (knowledgeBuilder!=null) {
      KnowledgeBase knowledgeBase = knowledgeBuilder.newKnowledgeBase();
      deploymentManager.getKnowledgeBaseCache().add(deployment.getId(), knowledgeBase);
    }
  }
}

從這裏能夠知道,文件必須是以.drl結尾,所謂部署就是將規則腳本添加到知識庫,用部署ID,做爲key,KnowledgeBase做爲value,加載到本地緩存,那麼解決辦法以下:

  • 從新部署流程,帶着更新後的規則腳本
  • 直接修改此類,設置一個監聽器,時時更新緩存
  • 這個類是實現的Deployer接口,而且在配置文件中可配置的,那麼最好的辦法就是,實現咱們本身的類來擴展
相關文章
相關標籤/搜索