利用Java的动态编译、动态加载结合EasyRules实现业务规则的动态性

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/85013989

作为一名专门写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、删除规则时,如果启用状态不能删除,需要先将其禁用。

十二、相关页面展示

本文内容到此结束了,有什么问题或者建议,欢迎在评论区进行探讨!

博文内容没有将涉及的代码全部展示。完整的代码已经打包上传到我的资源,大家可以去下载参考;还有,部分代码涉及到公司的框架,并没有包含在里面,可以换一种自己的方式实现的,抱歉。

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/85013989