1. 程式人生 > 實用技巧 >Drools-決策表使用2-整合springboot

Drools-決策表使用2-整合springboot

之前快速使用了決策表,本次接著整合springboot。

論大象裝冰箱需要幾步

  • 建springboot-maven工程、新增pom、配置application.yml,新增boot啟動類。
  • 將上篇demo的核心類,IOC到springboot中
  • 新增測試controller,執行時可以通過REST介面觸發規則重新整理動作

新建springboot工程

  • pom
    <drools.version>7.42.0.Final</drools.version>
    <springboot.version>2.2.5.RELEASE</springboot.version>
    
    <dependencies>
    	<dependency>
    		<groupId>org.drools</groupId>
    		<artifactId>drools-decisiontables</artifactId>
    	</dependency>
    
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-thymeleaf</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-web</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-actuator</artifactId>
    	</dependency>
    <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> 
    	<scope>runtime</scope> <optional>true</optional> </dependency> -->
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-configuration-processor</artifactId>
    		<optional>true</optional>
    	</dependency>
    </dependencies>
    
  • application.yml
    server:
      port: 8000
      
    spring:
      application:
        name: @project.artifactId@ 
      profiles:
        active: local
        
      thymeleaf:
        cache: false
    
    app:
      drools:
        # 配置Drools決策表文件路徑,可多個
        xlsFilePaths:
        - D:/temp/person_check.xls
    
  • 啟動類
    @SpringBootApplication
    public class BootDrools {
    	public static void main(String[] args) {
    		System.setProperty("drools.dateformat","yyyy-MM-dd");
    		SpringApplication.run(BootDrools.class, args);
    	}
    }
    

核心整合點

Kie相關類的IOC化 -> KieUtilService

  • KieHelper 私有單例化,只有首次啟動和規則重刷時才發生變更
  • 提供規則觸發方法
  • 提供規則重刷方法
  • application.yml 配置的檔案路徑注入
  • 程式碼:
    @Service
    @ConfigurationProperties(prefix = "app.drools")
    @Slf4j
    public class KieUtilService {
    //	@Autowired
    //	private DroolsRuleService droolsRuleService;
    	
    	private List<String> xlsFilePaths = new ArrayList<>();
    	
    	private KieHelper kieHelper;
    	
    	private final SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    	
    	@PostConstruct
    	public void init() {
    		reload();
    	}
    

注意KieSession

  • 注意KieHeplerKieContainerKieBaseKieSession的關係
  • 本次做法沒有考慮是否有狀態Session,直接使用方法/執行緒級Session,一個觸發動作就是一個新的Session
  • 程式碼:
    /**
     * 觸發規則
     * @param fact
     */
    public void fire(Object fact) {
    	this.fire(fact, new FireOption().setFireLimit(-1));
    }
    /**
     * 觸發規則,指定觸發配置
     * @param fact
     * @param fireLimit
     */
    public void fire(Object fact, FireOption option) {
    	log.info(">> fire, fact:{}, option:{}", fact, option);
    	
    	KieSession session = kieHelper.build().newKieSession();
    	//Drools全域性變數
    	if(CollUtil.isNotEmpty(option.getGlobalVariables())) {
    		for(Entry<String, Object> entry: option.getGlobalVariables().entrySet()) {
    			session.setGlobal(entry.getKey(), entry.getValue());
    		}
    	}
    	//fact放入工作內容
    	session.insert(fact);
    	//指定聚焦議程組
    	if(StrUtil.isNotBlank(option.getAgendaGroup())) {
    		session.getAgenda().getAgendaGroup(option.getAgendaGroup()).setFocus();
    	}
    	//觸發數量配置
    	int fireLimit = option.getFireLimit()==null ? -1 : option.getFireLimit();
    	int count = session.fireAllRules(fireLimit);
    	
    	log.info("本次觸發的規則數:{}", count);
    	session.dispose(); //方法級KieSession
    	
    	log.info("<< fire, fact:{}", fact);
    }
    

規則過載

  • 重新生成 KieHepler例項
  • 載入指定路徑的決策表文件
  • 過載方法:
    /**
     * 重新載入外接的決策表EXCEL規則檔案
     */
    public void reload() {
    	log.info(">> reload");
    	log.info("配置的xls規則檔案列表:{}", xlsFilePaths.toString());
    
    	// 載入規則
    	this.kieHelper = null;
    	KieHelper newKieHelper = new KieHelper();
    	try {
    		for (String path : xlsFilePaths) {
    			InputStream is = new FileInputStream(new File(path));
    			String drl = compiler.compile(is, InputType.XLS);
    			log.info("{} to compile drl:\n{}", path, drl);
    			newKieHelper.addContent(drl, ResourceType.DRL);
    		}
    		// 測試任意drl來源,測試通過
    		// List<DroolsRule> listAll = droolsRuleService.listAll();
    		// listAll.forEach(o -> newKieHelper.addContent(o.getDrlContent(), ResourceType.DRL));
    	} catch (FileNotFoundException e) {
    		log.error("檔案讀取異常", e);
    		throw new RuntimeException("檔案讀取異常", e);
    	}
    
    	// drl文字語法校驗
    	verifyDrl(newKieHelper);
    
    	this.kieHelper = newKieHelper;
    	log.info("<< reload");
    }
    
  • 測試controller:
    @RestController
    public class TestCtrller {
    	@Value("${spring.application.name}")
    	private String app_name;
    
    	@Autowired
    	private KieUtilService kieUtilService;
    
        //	@Autowired
        //	private AppDroolsYmlConfigBean ymlBean;
    
    	@GetMapping("/hello")
    	public Object hello() {
    		return app_name + "@" + LocalDateTime.now().toString();
    	}
    
    	@GetMapping("/rule/fire")
    	public Object ruleFire(PersonInfoEntity entity) {
    		kieUtilService.fire(entity);
    		// kieUtilService.fire(entity, new FireOption().setAgendaGroup("sign"));
    		return entity;
    	}
    
    	@GetMapping("/rule/reload")
    	public Object ruleReload() {
    		kieUtilService.reload();
    		return "OK";
    	}
    }
    

小結

  • 可以看出,本次demo並沒有採用網上主流的kie-spring整合方案,而是使用最少依賴結合IOC的方式。
    PS:主流的Drools整合Spring、Spring-Web以及SpringBoot的demo都可以在下方demo原始碼同級目錄中找到。
  • 單節點的整合相對還是簡單的,效能上咱也做不了太多改善了。但是多節點叢集下,比如Feign的負載均衡,只會過載某個節點,這是個問題。
  • 叢集下過載規則解決方案
    • MQ廣播
    • Nacos監聽(下篇預告)

參考