利用Java的動態編譯、動態載入結合EasyRules實現業務規則的動態性
作為一名專門寫bug的Java程式猿,相信大家都會遇到過這樣的問題:專案的業務邏輯很複雜,而且還經常變化,今天的一個辦理條件是小於5,明天就變成了大於10或者條件作廢。這就很頭疼了,裡面的數字可以抽取到配置檔案,但是大於和小於呢?條件作廢呢?
對於業務規則的複雜性,我們可以使用一些規則引擎來解決程式碼可讀性差的問題。市面上也有不少的規則引擎框架,開源的不開源的,收費的不收費的,我們這裡推薦使用的是EasyRules(https://github.com/j-easy/easy-rules)。
對於業務規則的變化性,部分變數的值可以抽取出來放到配置檔案裡面。但是大部分的需求變化,可不是改變一下變數的值那麼簡單,可能是一大段程式碼的重寫,這就需要利用Java6之後提供的動態編譯來實現了。
廢話不多說,精彩馬上來!
思路:在EasyRules中,一個if (...) {...}對應一條規則,也對應著一個類。這樣我們可以將這個類的資訊(原始碼、編譯後位元組碼、類名、所屬分組等)存到資料庫,以提供系統在執行時修改原始碼、重新編譯、動態載入、替換規則的功能。
具體實現:定義規則類,這個類除了有EasyRule的類名、原始碼、編譯後位元組碼等資訊之外,還有一些其它屬性,比如規則所屬分組、執行優先順序、啟動狀態等。當我們在頁面新增(或者修改)了原始碼,提交之後對其進行編譯,將得到類名和位元組碼,然後將這些資料儲存到資料庫。如果規則是啟用狀態,還要建立一個例項存放到到我們維護的一個map集合裡(如果存在同類名的例項就替換),以供規則引擎去呼叫。
一、EasyRules
什麼是EasyRules
首先EasyRule是一個規則引擎.這個名字由來是受到了Martin Fowler 的文章 Should I use a Rules Engine
You can build a simple rules engine yourself. All you need is to create a bunch of objects with conditions and actions, store them in a collection, and run through them to evaluate the conditions and execute the actions.
框架特點
- 輕量級類庫和容易上手
- 基於POJO的開發與註解的程式設計模型
- 方便且適用於java的抽象的業務模型規則
- 支援從簡單的規則建立組合規則
Useful abstractions to define business rules and apply them easily with Java
The ability to create composite rules from primitive ones
官方demo
1. First, define your rule..
Either in a declarative way using annotations:
@Rule(name = "weather rule", description = "if it rains then take an umbrella" )
public class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
}
Or in a programmatic way with a fluent API:
Rule weatherRule = new RuleBuilder()
.name("weather rule")
.description("if it rains then take an umbrella")
.when(facts -> facts.get("rain").equals(true))
.then(facts -> System.out.println("It rains, take an umbrella!"))
.build();
Or using an Expression Language:
Rule weatherRule = new MVELRule()
.name("weather rule")
.description("if it rains then take an umbrella")
.when("rain == true")
.then("System.out.println(\"It rains, take an umbrella!\");");
Or using a rule descriptor:
Like in the following weather-rule.yml
example file:
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
Rule weatherRule = MVELRuleFactory.createRuleFrom(new File("weather-rule.yml"));
2. Then, fire it!
public class Test {
public static void main(String[] args) {
// define facts
Facts facts = new Facts();
facts.put("rain", true);
// define rules
Rule weatherRule = ...
Rules rules = new Rules();
rules.register(weatherRule);
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
上面例子沒有提到的是,規則類可以設定執行優先順序,具體做法就是在類裡面定義一個返回型別是int的方法,然後在方法上面加一個註解@Priority。
二、規則資訊類
我們將類名叫做JavaRuleDO,所有屬性都對應著資料庫JAVA_RULE表的欄位。
這裡使用了lombok外掛,因此可以省略了getter、setter和toString方法,還有其它註解,挺好用的,感興趣的童鞋可以去看看。
@Getter
@Setter
@ToString
@Entity
@Table(name = "JAVA_RULE")
public class JavaRuleDO implements Serializable {
private static final long serialVersionUID = 830103606495004702L;
@Id
private Long id;
// 目標,一般指哪個系統
@Column
private String target;
// 檔名
@Column
private String fileName;
// 全類名
@Column
private String fullClassName;
// 類名
@Column
private String simpleClassName;
// 原始碼
@Column
private String srcCode;
// 編譯後位元組碼
@Column
private byte[] byteContent;
// 建立時間
@Column
private Date createTime;
// 建立使用者id
@Column
private Long createUserId = Consts.Entity.NULL_ID_PLACEHOLDER;
// 建立使用者名稱稱
@Column
private String createUserName;
// 更新時間
@Column
private Date updateTime;
// 更新使用者id
@Column
private Long updateUserId = Consts.Entity.NULL_ID_PLACEHOLDER;
// 更新使用者名稱稱
@Column
private String updateUserName;
// 是否已刪除,1是 0否
@Column
private Integer isDeleted = Consts.Entity.NOT_DELETED;
// 狀態,1有效 0無效
@Column
private Integer status = Consts.Entity.NOT_VALID;
// 組別名稱,一般指哪一系列規則
@Column
private String groupName;
// 順序(優先順序)
@Column
private Integer sort = Integer.MAX_VALUE;
// 規則名稱
@Column
private String name;
// 規則描述
@Column
private String description;
}
下面附上Oracle版本的建表SQL。
create table JAVA_RULE
(
id NUMBER(11) not null,
target VARCHAR2(32) not null,
file_name VARCHAR2(32) not null,
full_class_name VARCHAR2(64) not null,
simple_class_name VARCHAR2(32) not null,
src_code CLOB not null,
byte_content BLOB not null,
create_time DATE not null,
create_user_id NUMBER(11) not null,
create_user_name VARCHAR2(128) not null,
update_time DATE,
update_user_id NUMBER(11),
update_user_name VARCHAR2(128),
is_deleted NUMBER(1) default 0 not null,
status NUMBER(1) default 1 not null,
group_name VARCHAR2(32) not null,
sort NUMBER(3) default 999,
name VARCHAR2(512) not null,
description VARCHAR2(2048)
)
;
comment on column JAVA_RULE.id
is '主鍵';
comment on column JAVA_RULE.target
is '目標,一般指哪個系統';
comment on column JAVA_RULE.file_name
is '檔名';
comment on column JAVA_RULE.full_class_name
is '全類名';
comment on column JAVA_RULE.simple_class_name
is '類名';
comment on column JAVA_RULE.src_code
is '原始碼';
comment on column JAVA_RULE.byte_content
is '編譯後位元組碼';
comment on column JAVA_RULE.create_time
is '建立時間';
comment on column JAVA_RULE.create_user_id
is '建立使用者id';
comment on column JAVA_RULE.create_user_name
is '建立使用者名稱稱';
comment on column JAVA_RULE.update_time
is '更新時間';
comment on column JAVA_RULE.update_user_id
is '更新使用者id';
comment on column JAVA_RULE.update_user_name
is '更新使用者名稱稱';
comment on column JAVA_RULE.is_deleted
is '是否已刪除,1是 0否';
comment on column JAVA_RULE.status
is '狀態,1有效 0無效';
comment on column JAVA_RULE.group_name
is '組別名稱,一般指哪一系列規則';
comment on column JAVA_RULE.sort
is '順序(優先順序)';
comment on column JAVA_RULE.name
is '規則名稱';
comment on column JAVA_RULE.description
is '規則描述';
create unique index IDX_JAVA_RULE_FULL_CLASS_NAME on JAVA_RULE (FULL_CLASS_NAME);
alter table JAVA_RULE
add constraint PK_JAVA_RULE primary key (ID);
三、動態編譯
動態編譯一直是Java的夢想,從Java 6版本它開始支援動態編譯了,可以在執行期直接編譯.java檔案,執行.class。但是還要慎用,因為存在效能和安全問題。
下面提供了一個編譯原始碼的工具方法。如果編譯成功,返回一個CompileResult物件(自定義型別,下面給出了原始碼);如果編譯失敗,返回具體的編譯不通過原因。其中,Result是貴公司封裝的一個通用返回值包裝器,這裡不方便提供原始碼,抱歉。
程式碼1:動態編譯原始碼的工具方法(想了解更多細節可以去參考其它資料),暫時先去掉了後面會用到的其它工具方法。
/**
* 規則工具類
* @author z_hh
* @date 2018年12月7日
*/
@Slf4j
public class DynamicRuleUtils {
// 編譯版本
private static final String TARGET_CLASS_VERSION = "1.8";
/**
* auto fill in the java-name with code, return null if cannot find the public class
*
* @param javaSrc source code string
* @return return the Map, the KEY means ClassName, the VALUE means bytecode.
* @throws RuntimeException
*/
public static Result<CompileResult> compile(String javaSrc) throws RuntimeException {
Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
Matcher matcher = pattern.matcher(javaSrc);
if (matcher.find()) {
return compile(matcher.group(1) + ".java", javaSrc);
}
return Results.error("找不到類名稱!");
}
/**
* @param javaName the name of your public class,eg: <code>TestClass.java</code>
* @param javaSrc source code string
* @return return the Map, the KEY means ClassName, the VALUE means bytecode.
* @throws RuntimeException
*/
public static Result<CompileResult> compile(String javaName, String javaSrc) throws RuntimeException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
JavaFileObject javaFileObject = MemoryJavaFileManager.makeStringSource(javaName, javaSrc);
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
List<String> options = new ArrayList<>();
options.add("-target");
options.add(TARGET_CLASS_VERSION);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, collector, options, null,
Arrays.asList(javaFileObject));
if (task.call()) {
return Results.success(CompileResult.builder()
.mainClassFileName(javaName)
.byteCode(manager.getClassBytes())
.build());
}
String errorMessage = collector.getDiagnostics().stream()
.map(diagnostics -> diagnostics.toString())
.reduce("", (s1, s2) -> s1 + "\n" +s2);
return Results.error(errorMessage);
} catch (IOException e) {
log.error("編譯出錯啦!", e);
return Results.error(e.getMessage());
}
}
}
/**
* JavaFileManager that keeps compiled .class bytes in memory.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
final class MemoryJavaFileManager extends ForwardingJavaFileManager {
/**
* Java source file extension.
*/
private final static String EXT = ".java";
private Map<String, byte[]> classBytes;
public MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
classBytes = new HashMap<String, byte[]>();
}
public Map<String, byte[]> getClassBytes() {
return classBytes;
}
@Override
public void close() throws IOException {
classBytes = new HashMap<String, byte[]>();
}
@Override
public void flush() throws IOException {
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject {
final String code;
StringInputBuffer(String name, String code) {
super(toURI(name), Kind.SOURCE);
this.code = code;
}
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
@SuppressWarnings("unused")
public Reader openReader() {
return new StringReader(code);
}
}
/**
* A file object that stores Java bytecode into the classBytes map.
*/
private class ClassOutputBuffer extends SimpleJavaFileObject {
private String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new ClassOutputBuffer(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
static JavaFileObject makeStringSource(String name, String code) {
return new StringInputBuffer(name, code);
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if (name.endsWith(EXT))
newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///com/sun/script/java/java_source");
}
}
}
}
程式碼2: CompileResult類,之所以設定mainClassFileName,是因為我們暫時不支援內部類,只儲存頂級類的位元組碼。
/**
* 類編譯結果
* @author z_hh
* @date 2018年12月5日
*/
@Getter
@Setter
@Builder
@ToString
public class CompileResult {
// 主類全類名
private String mainClassFileName;
// 編譯出來的全類名和對應class位元組碼
private Map<String, byte[]> byteCode;
}
程式碼3:將編譯得到的資訊設定到實體物件。
// 編譯
private Result<JavaRuleDO> compiler(JavaRuleDO entity) {
Result<?> result = DynamicRuleUtils.compile(entity.getSrcCode());
if (result.isError()) {
return (Result<JavaRuleDO>) result;
}
CompileResult compileResult = (CompileResult) result.get();
for (String classFullName : compileResult.getByteCode().keySet()) {
int lastIndex = classFullName.lastIndexOf(".");
String simpleName = lastIndex != -1 ? classFullName.substring(lastIndex + 1) : classFullName,
fileName = compileResult.getMainClassFileName();
// 只要最外層的類
if (fileName.startsWith(simpleName)) {
entity.setFileName(fileName);
entity.setFullClassName(classFullName);
entity.setSimpleClassName(simpleName);
entity.setByteContent(compileResult.getByteCode().get(classFullName));
return Results.success(entity);
}
}
return Results.error("沒有找到最外層類!");
}
四、動態載入
在動態編譯階段,我們已經得到了原始碼對應的位元組碼,這樣就可以將其載入到JVM裡面了。
在這裡有必要提兩點:第一,在Java虛擬機器層面,相同的一個類,除了有相同的全類名以外,還要由相同的類載入器進行載入;第二,類載入的雙親委派模型,工作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父類載入器反饋自己無法完成這個載入請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入。
因此,我們需要定義自己的類載入器,每次將class位元組碼載入到JVM都需要建立一個新的類載入器物件。主要是重寫findClass方法,將我們傳進去的位元組碼陣列進行載入。
/*
* a temp memory class loader
*/
private static class MemoryClassLoader extends URLClassLoader {
Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public MemoryClassLoader(Map<String, byte[]> classBytes) {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
this.classBytes.putAll(classBytes);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf == null) {
return super.findClass(name);
}
classBytes.remove(name);
return defineClass(name, buf, 0, buf.length);
}
}
具體使用方式。
/**
* 從JavaRuleDO獲取規則Class物件
* @param javaRule
* @throws Exception
*/
public static Class<?> getRuleClass(JavaRuleDO javaRule) throws Exception {
Map<String, byte[]> bytecode = new HashMap<>();
String fullName = Objects.requireNonNull(javaRule.getFullClassName(), "全類名不能為空!");
bytecode.put(fullName, javaRule.getByteContent());
try (MemoryClassLoader classLoader = new MemoryClassLoader(bytecode)) {
return classLoader.findClass(fullName);
} catch (Exception e) {
log.error("載入類{}異常!", fullName);
throw e;
}
}
五、定義規則基類
這裡強制所有規則類都要繼承我們定義好的規則基類,有兩個原因:
1、定義優先順序屬性,並提供set方法供外部設值、get方法供規則引擎獲取。
2、重寫hashCode方法和equals方法,以讓容器(HashSet)認定相同型別的兩個元素是相同的。
/**
* 規則基類
* @author z_hh
* @date 2018年12月12日
*/
public class BaseRule {
private int priority = Integer.MAX_VALUE;
/*重寫equals方法和hashCode方法,讓Set集合判定同類型的兩個物件相同*/
@Override
public boolean equals(Object obj) {
return Objects.nonNull(obj)
&& Objects.equals(this.getClass().getName(), obj.getClass().getName());
}
@Override
public int hashCode() {
return Objects.hashCode(this.getClass().getName());
}
/**
* 獲取優先順序
*/
@Priority
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
}
六、維護規則容器
首先我們定義一個容器介面,宣告一些需要提供的介面。畢竟面向介面程式設計,方便以後擴充套件容器型別。
/**
* Java規則類儲存器
* @author z_hh
* @date 2018年12月12日
*/
public interface JavaRuleStorage {
/**
* 容器是否包含指定規則
* @param javaRule
* @return
*/
boolean contains(String groupName, BaseRule rule);
/**
* 新增規則到容器
* @param javaRule
*/
boolean add(String groupName, BaseRule rule);
/**
* 批量新增規則到容器的指定組
* @param javaRules
*/
boolean batchAdd(String groupName, Iterable<? extends BaseRule> rules);
/**
* 從容器移除指定規則
* @param group
*/
boolean remove(String groupName, BaseRule rule);
/**
* 從容器移除指定組的規則
* @param group
*/
boolean remove(String group);
/**
* 從容器獲取指定組的所有規則
* @param group
* @return
*/
Collection<BaseRule> listObjByGroup(String group);
}
然後提供一個map實現版。或者使用Spring IOC容器,但我感覺沒有那麼靈活。這裡的Multimap是Google Guava提供的集合類工具,類似於JDK裡面的Map<K, Set<E>>。需要注意的是,新增元素的時候,如果存在相同key和相同value型別的元素,需要先將其移除。
/**
* Java規則類儲存器Map版
* @author z_hh
* @date 2018年12月12日
*/
public class MapJavaRuleStorage implements JavaRuleStorage {
private final Multimap<String, BaseRule> map = HashMultimap.create();
@Override
public boolean contains(String groupName, BaseRule rule) {
return map.containsEntry(groupName, rule);
}
@Override
public boolean add(String groupName, BaseRule rule) {
// 如果原來有,就先刪除掉
if (map.containsEntry(groupName, rule)) {
map.remove(groupName, rule);
}
return map.put(groupName, rule);
}
@Override
public boolean batchAdd(String groupName, Iterable<? extends BaseRule> rules) {
return map.putAll(groupName, rules);
}
@Override
public boolean remove(String groupName, BaseRule rule) {
return map.remove(groupName, rule);
}
@Override
public boolean remove(String group) {
return !map.removeAll(group).isEmpty();
}
@Override
public Collection<BaseRule> listObjByGroup(String group) {
return map.get(group);
}
}
七、新增規則到容器、將規則從容器移除
獲取規則例項的工具方法(其中getRuleClass(...)方法上面已提供)。
/**
* 從JavaRuleDO獲取規則例項物件
* @param javaRule
* @throws Exception
*/
public static BaseRule getRuleInstance(JavaRuleDO javaRule) throws Exception {
try {
BaseRule rule = (BaseRule) getRuleClass(javaRule).newInstance();
// 設定優先順序
rule.setPriority(javaRule.getSort());
return rule;
} catch (Exception e) {
log.error("從JavaRuleDO獲取規則例項異常!", e);
throw e;
}
}
新增規則到容器,其中entity為JavaRuleDO例項。
/**
* 新增規則到容器
* @param entity
* @return
*/
private Result<JavaRuleDO> addRuleToStorage(JavaRuleDO entity) {
try {
BaseRule rule = DynamicRuleUtils.getRuleInstance(entity);
return javaRuleStorage.add(entity.getGroupName(), rule) ? Results.success(entity)
: Results.error("新增規則到容器失敗!");
} catch (Exception e) {
log.error("新增規則{}到容器異常!", entity.getName(), e);
return Results.error("新增規則到容器異常!");
}
}
將規則從容器移除,其中entity為JavaRuleDO例項。
String groupName = entity.getGroupName();
try {
BaseRule rule = DynamicRuleUtils.getRuleInstance(entity);
if (javaRuleStorage.contains(groupName, rule) && !javaRuleStorage.remove(groupName, rule)) {
return Results.error("從容器移除規則失敗!");
}
} catch (Exception e) {
log.error("從容器移除規則{}異常!", entity.getName(), e);
return Results.error("從容器移除規則異常!");
}
八、封裝規則使用元件
為了更方便使用規則引擎,我們特意建立了一個DynamicRuleManager,以實現鏈式呼叫。
/**
* 動態規則管理器
* @author z_hh
* @date 2018年12月12日
*/
@Component("dynamicRuleManager")
public class DynamicRuleManager {
public Builder builder() {
return new Builder(this);
}
public class Builder {
private Rules rules = new Rules();
private Facts facts = new Facts();
private RulesEngine engine = new DefaultRulesEngine();
private JavaRuleStorage javaRuleStorage;
public Builder(DynamicRuleManager dynamicRuleManager) {
javaRuleStorage = dynamicRuleManager.javaRuleStorage;
}
/**
* 設定引數,該引數為值傳遞,在規則裡面或者執行完之後可以取到
* @param name
* @param value
* @return
*/
public Builder setParameter(String name, Object value) {
facts.put(name, value);
return this;
}
/**
* 增加規則組(將指定所屬分組的所有啟用規則新增進來)
* @param groupName
* @return
*/
public Builder addRuleGroup(String groupName) {
Collection<BaseRule> rs = javaRuleStorage.listObjByGroup(groupName);
rs.stream().forEach(rules::register);
return this;
}
/**
* 執行規則引擎
*/
public Builder run() {
engine.fire(rules, facts);
return this;
}
/**
* 獲取指定引數,並轉為指定型別
* @param pName
* @param pType
* @return
*/
public <T> T getParameter(String pName, Class<T> pType) {
return facts.get(pName);
}
}
@Autowired
private JavaRuleStorage javaRuleStorage;
}
@Configuration
public class RuleDefaultConf {
@Bean
@ConditionalOnMissingBean
public JavaRuleStorage javaRuleStorage() {
return new MapJavaRuleStorage();
}
}
九、使用案例
1、建立規則。
@Rule
public class DemoRule1 extends BaseRule {
@Condition
public boolean when(@Fact("param1") String param1) {
System.out.println("我是引數1,value=" + param1);
return true;
}
@Action
public void then(@Fact("param2") String param2) {
System.out.println("我是引數2,value=" + param2);
}
}
2、呼叫規則。
Builder builder = dynamicRuleManager.builder()
.setParameter("param1", "Hello")
.setParameter("param2", "World")
.addRuleGroup("testRule")
.run();
String param1 = builder.getParameter("param1", String.class);
String param2 = builder.getParameter("param2", String.class);
System.out.println(param1 + " " + param2);
3、執行結果。
十、系統啟動時將啟用的規則新增到容器
我們設定了dynamic.rule.target(目標,所屬系統)引數從配置檔案獲取。
/**
* 應用啟動監聽器
* @author z_hh
* @date 2018年12月10日
*/
@Slf4j
@WebListener
public class AppRunListener implements ServletContextListener {
@Value("${dynamic.rule.target}")
private String ruleTarget;
/**
* 將指定組的javaRule物件裝進容器
*/
@Override
public void contextInitialized(ServletContextEvent event) {
if (StringUtils2.notEmpty(ruleTarget)) {
List<JavaRuleDO> javaRules = javaRuleService.createJpaQuery()
.where("status", Consts.Entity.IS_VALID)
.where("target", SqlFieldOperatorEnum.IN, Arrays.asList(ruleTarget.split(",")))
.list();
javaRules.stream()
.forEach(javaRule -> {
try {
BaseRule rule = DynamicRuleUtils.getRuleInstance(javaRule);
if (!javaRuleStorage.add(javaRule.getGroupName(), rule)) {
log.warn("新增規則{}到容器失敗!", javaRule.getName());
javaRule.setStatus(Consts.Entity.NOT_VALID);
javaRuleService.save(javaRule);
}
log.info("添加了規則{}到容器", javaRule.getFullClassName());
} catch (Exception e) {
log.warn("新增規則{}到容器異常!", javaRule.getName());
javaRule.setStatus(Consts.Entity.NOT_VALID);
javaRuleService.save(javaRule);
}
});
}
}
@Override
public void contextDestroyed(ServletContextEvent event) {
//
}
@Autowired
private JavaRuleService javaRuleService;
@Autowired
private JavaRuleStorage javaRuleStorage;
}
十一、其它說明
1、當我們在管理頁面新增或者修改規則時,如果狀態為啟用,後臺應該需要在編譯之後建立規則例項並放進容器;反之,如果狀態為禁用,後臺就判斷容器裡是否有該規則類的例項,有的話需要將其移除。
2、管理頁面的啟用和禁用,對應著這個規則類例項新增到容器和從容器裡面移除的操作。
3、刪除規則時,如果啟用狀態不能刪除,需要先將其禁用。
十二、相關頁面展示
本文內容到此結束了,有什麼問題或者建議,歡迎在評論區進行探討!
博文內容沒有將涉及的程式碼全部展示。完整的程式碼已經打包上傳到我的資源,大家可以去下載參考;還有,部分程式碼涉及到公司的框架,並沒有包含在裡面,可以換一種自己的方式實現的,抱歉。