作为一名专门写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、删除规则时,如果启用状态不能删除,需要先将其禁用。
十二、相关页面展示
本文内容到此结束了,有什么问题或者建议,欢迎在评论区进行探讨!
博文内容没有将涉及的代码全部展示。完整的代码已经打包上传到我的资源,大家可以去下载参考;还有,部分代码涉及到公司的框架,并没有包含在里面,可以换一种自己的方式实现的,抱歉。