官網地址:http://drools.org/java
Drools is a Business Rules Management System (BRMS) solution.web
It provides a core Business Rules Engine (BRE), a web authoring and rules management application (Drools Workbench) and an Eclipse IDE plugin for core development.spring
最近有個消費返現和後付費保險類型項目,要求根據不一樣規則進行消費返現及保險靜默投保etc. ,爲避免過多硬編碼ifelse邏輯判斷,影響程序可讀性及削弱程序可擴展性,所以引入了Drools規則引擎。api
至於規則引擎究竟是啥,在這裏就不贅述了,google一下,你就知道。session
下面基於一個簡單的Mock User Register模擬流程,簡單介紹一下關於Spring+Drools集成實現流程。app
新用戶規則: 1.系統Mock生成用戶註冊數據,並進入Drools規則引擎處理; 2.對於新註冊用戶設定用戶鎖定狀態,並初始用戶等級爲3級,覆蓋新用戶標識爲非; 非新用戶規則: 1.設定用戶非鎖定狀態,每次Mock Code執行,用戶等級+1(規則比較隨意);
添加Drools Pom依賴ide
<pro..> <drools-version>6.4.0.Final</drools-version> </pro..> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>${drools-version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools-version}</version> </dependency>
User實體Bean定義ui
@Getter @Setter @Access(AccessType.FIELD) @Accessors(chain = true) @MetaData(value = "登錄/認證用戶信息" , comments = "monster-web / monster-web-admin模塊使用統一的auth信息") @Entity @Table(name = "auth_MsUser" , uniqueConstraints = @UniqueConstraint(columnNames ={"authUid","authType"})) @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class User extends BaseNativeEntity { …… …… @MetaData(value="用戶全局惟一標識" , comments = "同時做爲系統用戶密碼Salt值,Salt形式 [authUuid]") @Column(length = 64 , nullable = false) private String authUuid; @MetaData(value="authUid,登錄帳號") @Column(length = 128 , nullable = false) private String authUid; @MetaData(value="密碼",comments = "MD5加密串,格式:MD5({Salt}+sourcePassword)") @Column(length = 128 , nullable = false) private String password; @MetaData(value="用戶類型") @Column(length = 32 , nullable = false) @Enumerated(EnumType.STRING) private AuthTypeEnum authType; @MetaData(value = "身份證號碼") @Column(length = 32) private String idCardNo; @MetaData(value="性別") @Column(length = 32) @Enumerated(EnumType.STRING) private GenderEnum gender; @MetaData(value = "REST訪問Token") @Column private String accessToken; @MetaData(value = "訪問Token過時時間") @DateTimeFormat(pattern = DateUtils.DEFAULT_TIME_FORMAT) @JsonFormat(pattern = DateUtils.DEFAULT_TIME_FORMAT) private Date accessTokenExpireTime; @MetaData(value = "是否新用戶",tooltips = "用於新用戶相關業務規則") @Column private Boolean isNew = Boolean.TRUE; @MetaData(value = "帳戶未鎖定標誌", tooltips = "帳號鎖定後沒法登陸") @Column private Boolean accountNonLocked = Boolean.TRUE; …… …… }
Drools相關Bean定義this
提供Drools資源輔助類google
package org.monster.drools.technorage; import org.kie.api.io.ResourceType; @setter @getter public class DroolsResource { private String path; private ResourcePathType pathType; private ResourceType type; private String username = null; private String password = null; /** * * @param path * The path to this resource. * @param pathType * The type of path (FILE, URL, etc). * @param type * The type of resource (DRL, Binary package, DSL, etc) */ public DroolsResource(String path, ResourcePathType pathType, ResourceType type) { this.path = path; this.pathType = pathType; this.type = type; } /** * * @param path * The path to this resource. * @param pathType * The type of path (FILE, URL, etc). * @param type * The type of resource (DRL, Binary package, DSL, etc) * @param username * The user name for connecting to the resource. * @param password * The password for connecting to the resource. */ public DroolsResource(String path, ResourcePathType pathType, ResourceType type, String username, String password) { this.path = path; this.pathType = pathType; this.type = type; this.username = username; this.password = password; } }
定義本地接口,並實現Drools提供的KieServices與KieContainer、KieSession接口
import org.kie.api.KieServices; public interface KieServicesBean extends KieServices { }
package org.monster.drools.technorage.spring; import org.kie.api.runtime.KieContainer; public interface KieContainerBean extends KieContainer { }
package org.monster.drools.technorage.spring; import org.kie.api.runtime.KieSession; public interface KieSessionBean extends KieSession { }
及其分別實現類
public class DefaultKieServicesBean implements KieServicesBean { private static Logger log = LoggerFactory.getLogger(DefaultKieServicesBean.class); private DroolsResource[] resources; private KieServices kieServices; private KieFileSystem kfs; public DefaultKieServicesBean(DroolsResource[] resources) throws KieBuildException { log.info("Initialising KnowledgeEnvironment with resources: " + this.resources); this.resources = resources; createAndBuildKieServices(resources); } /** * Initialises the {@link org.kie.api.KieServices} by downloading the package from the * Guvnor REST interface, at the location defined in the URL. * * @param url The URL of the package via the Guvnor REST API. * @throws KieBuildException */ public DefaultKieServicesBean(String url) throws KieBuildException { log.info("Initialising KnowledgeEnvironment with resources: " + this.resources); this.resources = new DroolsResource[] { new DroolsResource(url, ResourcePathType.URL, ResourceType.PKG )}; createAndBuildKieServices(resources); } /** * 根據提供的URL生成{@link org.kie.api.KieServices} * * @param url The URL of the package via the Guvnor REST API. * @param username The Guvnor user name. * @param password The Guvnor password. * @throws KieBuildException */ public DefaultKieServicesBean(String url, String username, String password) throws KieBuildException { this.resources = new DroolsResource[] { new DroolsResource(url, ResourcePathType.URL, ResourceType.PKG, username, password )}; createAndBuildKieServices(resources); } /** * 根據提供的DroolsResource建立 {@link org.kie.api.KieServices} * * @param resources * An array of {@link DroolsResource} indicating where the * various resources should be loaded from. These could be * classpath, file or URL resources. * @return A new {@link org.kie.api.runtime.KieContainer}. * @throws KieBuildException */ private void createAndBuildKieServices(DroolsResource[] resources) throws KieBuildException { this.kieServices = KieServices.Factory.get(); this.kfs = newKieFileSystem(); for (DroolsResource resource : resources) { log.info("Resource: " + resource.getType() + ", path type=" + resource.getPathType() + ", path=" + resource.getPath()); switch (resource.getPathType()) { case CLASSPATH: this.kfs.write(ResourceFactory.newClassPathResource(resource.getPath())); break; case FILE: this.kfs.write(ResourceFactory.newFileResource(resource.getPath())); break; case URL: UrlResource urlResource = (UrlResource) ResourceFactory .newUrlResource(resource.getPath()); if (resource.getUsername() != null) { log.info("Setting authentication for: " + resource.getUsername()); urlResource.setBasicAuthentication("enabled"); urlResource.setUsername(resource.getUsername()); urlResource.setPassword(resource.getPassword()); } this.kfs.write(urlResource); break; default: throw new IllegalArgumentException( "Unable to build this resource path type."); } } KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll(); if (kieBuilder.getResults().hasMessages(Level.ERROR)) { List<Message> errors = kieBuilder.getResults().getMessages(Level.ERROR); StringBuilder sb = new StringBuilder("Errors:"); for (Message msg : errors) { sb.append("\n " + prettyBuildMessage(msg)); } throw new KieBuildException(sb.toString()); } log.info("KieServices built: " + toString()); } private static String prettyBuildMessage(Message msg) { return "Message: {" + "id="+ msg.getId() + ", level=" + msg.getLevel() + ", path=" + msg.getPath() + ", line=" + msg.getLine() + ", column=" + msg.getColumn() + ", text=\"" + msg.getText() + "\"" + "}"; } //省略相關實現方法 }
public class DefaultKieContainerBean implements KieContainerBean { private KieServicesBean kieServices; private KieContainer kieContainer; public DefaultKieContainerBean(KieServicesBean kieServicesBean) { this.kieServices = kieServicesBean; this.kieContainer = this.kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId()); } /** …… …… */ }
public class DefaultKieSessionBean implements KieSessionBean { private static Logger log = LoggerFactory.getLogger(DefaultKieSessionBean.class); private KieSession kieSession; public DefaultKieSessionBean(KieServicesBean kieServices, KieContainerBean kieContainer) { this(kieServices, kieContainer, null); } public DefaultKieSessionBean(KieServicesBean kieServices, KieContainerBean kieContainer, Properties droolsProperties) { log.info("Initialising session..."); KieSessionConfiguration conf; if (droolsProperties == null) { conf = SessionConfiguration.getDefaultInstance(); } else { conf = SessionConfiguration.newInstance(droolsProperties);//new SessionConfiguration(droolsProperties); } this.kieSession = kieContainer.newKieSession(conf); } public void addEventListener(RuleRuntimeEventListener listener) { kieSession.addEventListener(listener); } public void addEventListener(ProcessEventListener listener) { kieSession.addEventListener(listener); } public ProcessInstance startProcess(String processId) { return kieSession.startProcess(processId); } public void removeEventListener(RuleRuntimeEventListener listener) { kieSession.removeEventListener(listener); } public int fireAllRules() { return kieSession.fireAllRules(); } public void removeEventListener(ProcessEventListener listener) { kieSession.removeEventListener(listener); } public <T extends SessionClock> T getSessionClock() { return kieSession.getSessionClock(); } public int fireAllRules(int max) { return kieSession.fireAllRules(max); } public Collection<RuleRuntimeEventListener> getWorkingMemoryEventListeners() { return kieSession.getRuleRuntimeEventListeners(); } public Collection<ProcessEventListener> getProcessEventListeners() { return kieSession.getProcessEventListeners(); } public void setGlobal(String identifier, Object value) { kieSession.setGlobal(identifier, value); } public void halt() { kieSession.halt(); } public ProcessInstance startProcess(String processId, Map<String, Object> parameters) { return kieSession.startProcess(processId, parameters); } public void addEventListener(AgendaEventListener listener) { kieSession.addEventListener(listener); } public Object getGlobal(String identifier) { return kieSession.getGlobal(identifier); } public Globals getGlobals() { return kieSession.getGlobals(); } public Calendars getCalendars() { return kieSession.getCalendars(); } public void removeEventListener(AgendaEventListener listener) { kieSession.removeEventListener(listener); } public Environment getEnvironment() { return kieSession.getEnvironment(); } public KieBase getKieBase() { return kieSession.getKieBase(); } public int fireAllRules(AgendaFilter agendaFilter) { return kieSession.fireAllRules(agendaFilter); } public void registerChannel(String name, Channel channel) { kieSession.registerChannel(name, channel); } public Collection<AgendaEventListener> getAgendaEventListeners() { return kieSession.getAgendaEventListeners(); } public String getEntryPointId() { return kieSession.getEntryPointId(); } public void unregisterChannel(String name) { kieSession.unregisterChannel(name); } public Map<String, Channel> getChannels() { return kieSession.getChannels(); } public Agenda getAgenda() { return kieSession.getAgenda(); } public int fireAllRules(AgendaFilter agendaFilter, int max) { return kieSession.fireAllRules(agendaFilter, max); } public FactHandle insert(Object object) { return kieSession.insert(object); } public KieSessionConfiguration getSessionConfiguration() { return kieSession.getSessionConfiguration(); } @Override public EntryPoint getEntryPoint(String name) { return kieSession.getEntryPoint(name); } public ProcessInstance createProcessInstance(String processId, Map<String, Object> parameters) { return kieSession.createProcessInstance(processId, parameters); } @Deprecated public void retract(FactHandle handle) { kieSession.retract(handle); } public Collection<? extends EntryPoint> getEntryPoints() { return kieSession.getEntryPoints(); } public void fireUntilHalt() { kieSession.fireUntilHalt(); } public <T> T execute(Command<T> command) { return kieSession.execute(command); } public void delete(FactHandle handle) { kieSession.delete(handle); } @Override public void delete(FactHandle handle, FactHandle.State fhState) { } public QueryResults getQueryResults(String query, Object... arguments) { return kieSession.getQueryResults(query, arguments); } public void update(FactHandle handle, Object object) { kieSession.update(handle, object); } public void fireUntilHalt(AgendaFilter agendaFilter) { kieSession.fireUntilHalt(agendaFilter); } public FactHandle getFactHandle(Object object) { return kieSession.getFactHandle(object); } public LiveQuery openLiveQuery(String query, Object[] arguments, ViewChangedEventListener listener) { return kieSession.openLiveQuery(query, arguments, listener); } public ProcessInstance startProcessInstance(long processInstanceId) { return kieSession.startProcessInstance(processInstanceId); } public Object getObject(FactHandle factHandle) { return kieSession.getObject(factHandle); } public int getId() { return kieSession.getId(); } @Override public long getIdentifier() { return 0; } public void signalEvent(String type, Object event) { kieSession.signalEvent(type, event); } public void dispose() { kieSession.dispose(); } public Collection<? extends Object> getObjects() { return kieSession.getObjects(); } public void destroy() { kieSession.destroy(); } public void signalEvent(String type, Object event, long processInstanceId) { kieSession.signalEvent(type, event, processInstanceId); } public Collection<? extends Object> getObjects(ObjectFilter filter) { return kieSession.getObjects(filter); } public <T extends FactHandle> Collection<T> getFactHandles() { return kieSession.getFactHandles(); } public <T extends FactHandle> Collection<T> getFactHandles( ObjectFilter filter) { return kieSession.getFactHandles(filter); } public Collection<ProcessInstance> getProcessInstances() { return kieSession.getProcessInstances(); } public long getFactCount() { return kieSession.getFactCount(); } public ProcessInstance getProcessInstance(long processInstanceId) { return kieSession.getProcessInstance(processInstanceId); } public ProcessInstance getProcessInstance(long processInstanceId, boolean readonly) { return kieSession.getProcessInstance(processInstanceId, readonly); } public void abortProcessInstance(long processInstanceId) { kieSession.abortProcessInstance(processInstanceId); } public WorkItemManager getWorkItemManager() { return kieSession.getWorkItemManager(); } @Override public KieRuntimeLogger getLogger() { return kieSession.getLogger(); } @Override public Collection<RuleRuntimeEventListener> getRuleRuntimeEventListeners() { return kieSession.getRuleRuntimeEventListeners(); } }
集成Spring,交由spring託管
@Configuration //@Profile("drools") public class BaseKieConfig { @Bean(name="kieServices") public KieServicesBean kieServices() throws KieBuildException { DroolsResource [] droolsResources = new DroolsResource[]{ new DroolsResource("rules/user-level.drl", ResourcePathType.CLASSPATH , ResourceType.DRL) }; KieServicesBean kieServicesBean = new DefaultKieServicesBean(droolsResources); return kieServicesBean; } @Bean(name="kieContainer") public KieContainerBean kieContainer(KieServicesBean kieServices){ KieContainerBean kieContainer = new DefaultKieContainerBean(kieServices); return kieContainer; } }
定義相關業務接口
public interface UserRuleService<T> { List<T> userRegister( List<User> users ); List<T> remUsers( List<User> users ); List<T> checkUserStatus(); }
@Service @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON , proxyMode = ScopedProxyMode.INTERFACES) public class UserRuleServiceImpl implements UserRuleService<User> ,Serializable { private Logger logger = LoggerFactory.getLogger(UserRuleServiceImpl.class); public KieSessionBean kieSession; private TrackingAgendaEventListener agendaEventListener; private TrackingWorkingMemoryEventListener workingMemoryEventListener; private Map<String , FactHandle> fact2User = Maps.newHashMap(); private FactFinder<User> userFactFinder = new FactFinder<User>(User.class); @Autowired public UserRuleServiceImpl(@Qualifier("kieServices") KieServicesBean kieServices , @Qualifier("kieContainer") KieContainerBean kieContainer){ System.setProperty("drools.negatable", "on"); kieSession = new DefaultKieSessionBean(kieServices , kieContainer); agendaEventListener = new TrackingAgendaEventListener(); workingMemoryEventListener = new TrackingWorkingMemoryEventListener(); kieSession.addEventListener(agendaEventListener); kieSession.addEventListener(workingMemoryEventListener); } @Override public List<User> userRegister(List<User> users) { for ( User user : users ) { if(logger.isDebugEnabled()){ logger.debug("執行用戶註冊規則模板,對應用戶:{}",user.toString()); } if(!fact2User.containsKey(user.getId())){ FactHandle userFireHandle = kieSession.insert(user); fact2User.put(String.valueOf(user.getId()) , userFireHandle); } } kieSession.fireAllRules(); List<User> results = userFactFinder.findFacts(kieSession); pause(); kieSession.dispose(); return results; } @Override public List<User> remUsers(List<User> users) { for( User user : users ) { if(fact2User.containsKey(String.valueOf(user.getId()))) { kieSession.delete(fact2User.get(String.valueOf(user.getId()))); fact2User.remove(user); } } kieSession.fireAllRules(); List<User> result = userFactFinder.findFacts(kieSession); return result; } @Override public List<User> checkUserStatus() { return null; } public static void pause() { System.out.println( "Pressure enter to continue" ); Scanner keyboard = new Scanner(System.in); keyboard.nextLine(); } }
Mock代碼
…… …… …… int userCnt = countTable(User.class); for( int i = userCnt ; (i < INIT_USER_CNT && i<userCnt+5) ; i ++ ) { String salt = UidUtils.UID(); String format = String.format("%06d" , i); User user = MockUtils.buildMockEntity(User.class); user.setAuthUid("19999"+format); user.setRealName("用戶" + format); user.setNickName("暱稱" + format); user.setAuthUuid(salt); user.setAuthType(User.AuthTypeEnum.SYS); user.setPassword(passwordService.encryptPassword("123456" , salt)); user.setIdCardNo("110010" + String.format("%012d" , i)); user.setAccessToken(StringUtils.EMPTY); //user.setAccessToken(salt); users.add(user); } if(CollectionUtils.isEmpty(users)) { users = userService.findAll(); } //規則引擎服務處理 users = (List<User>) userRuleService.userRegister(users); //存儲用戶信息 userService.save(users);
定義規則文件(user-register.drl)
//User-Level規則 package rules import org.monster.core.auth.entity.User; rule RegistNewUserRule //ruleflow-group "Basic-User-Group" when $user:User(isNew == Boolean.TRUE) then //1.設置解除鎖定狀態 $user.setAccountNonLocked(false); //2.用戶註冊送積分 $user.setUserLevel(3); $user.setIsNew(Boolean.FALSE); //3.根據性別 //System.out.println("++++++++++ Is New Flg True , [User:" + $user.toString() + "]"); end rule RegistNewUserRuleNotNew //ruleflow-group "Basic-User-Group" when $user:User(isNew == false) then //1.設置鎖定狀態 $user.setAccountNonLocked(true); //2.老用戶統一設定每次啓動增長一級 $user.setUserLevel($user.getUserLevel()+1); //System.out.println("---------- Is New Flg False, [User:" + $user.toString() +"]"); end
————————————————————————————————————————————————————————————————————————————————
1.1 容器經過ClassPathResource獲取KieFileSystem
//獲取KieFileSystem this.kfs.write(ResourceFactory.newClassPathResource(resource.getPath()));
1.2.並經過Factory.get()方法獲取Service實例,進行相關裝載校驗
//獲取kieServices實例 this.kieServices = KieServices.Factory.get(); …… KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll(); if (kieBuilder.getResults().hasMessages(Level.ERROR)) { List<Message> errors = kieBuilder.getResults().getMessages(Level.ERROR); StringBuilder sb = new StringBuilder("Errors:"); for (Message msg : errors) { sb.append("\n " + prettyBuildMessage(msg)); } throw new KieBuildException(sb.toString()); }
經過KieServices實例,構造KieContainer
this.kieContainer = this.kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
經過KieContainer及相關KieSessionConfig獲取current working Session對象
KieSessionConfiguration conf; if (droolsProperties == null) { conf = SessionConfiguration.getDefaultInstance(); } else { conf = SessionConfiguration.newInstance(droolsProperties);//new SessionConfiguration(droolsProperties); } this.kieSession = kieContainer.newKieSession(conf);
時間匆忙寫的比較亂,基本貼的代碼。有空整理