目录
1.规则创建方式
1.1.@Rule注解
1.2.链式编程
1.3.表达式方式
1.4.文件脚本DSL方式
2.创建的规则类(产品类)
3.规则工厂类
3.1 RuleDefinition类
3.2 组合规则创建
3.3 单一规则创建
EasyRule框架的源码解析见上篇文章:EasyRule源码:EasyRule框架源码分析
本文主要对EasyRule框架中应用的工厂方法模式---规则创建进行源码解析;
1.规则创建方式
EasyRule框架支持的创建规则的方法有很多种,如下是几种规则创建的方式:
1.1.@Rule注解
利用注解方式完成规则的创建:
@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!");
}
}
1.2.链式编程
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();
1.3.表达式方式
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!\");");
1.4.文件脚本DSL方式
weather-rule.yml文件定义:
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
或者weather-rule.json文件定义:
[
{
"name": "weather rule",
"description": "if it rains then take an umbrella",
"priority": 1,
"condition": "rain == true",
"actions": [
"System.out.println(\"It rains, take an umbrella!\");"
]
}
]
其中,前3种方式都是通过Java编码的方式实现规则创建,第4种文件脚本DSL方式通过指定json或yml文件的方式完成规则创建,该种方式的好处包括:
- 规则创建更加简单易懂
- 可以支持规则热发布
在EasyRule框架源码中,对文件脚本DSL方式的解析是通过巧妙利用工厂方法模式来完成规则创建的,这里的抽象工厂类就是AbstractRuleFactory,创建的产品类是Rule具体实现类,下面重点从产品类和工厂类角度对该部分进行源码解析;
2.创建的规则类(产品类)
具体的规则类整体类图如下:
规则类包括单一规则(SpELRule&MVELRule&JexlRule)和组合规则(CompositeRule),说明如下:
BasicRule:Rule接口的基础实现类,管理规则名称、描述和优先级
DefaultRule:默认规则实现类,包含Condition和多个Action
SpELRule&MVELRule&JexlRule:支持SpEL、MVEL、Jexl表达式定义的Condition和Action
CompositeRule:组合规则,对多个规则组合管理
ActivationRuleGroup&ConditionalRuleGroup&UnitRuleGroup:封装不同的组合规则管理策略
以SpELRule为例说明,SpELRule源码如下:
/**
* A {@link Rule} implementation that uses
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions">SpEL</a>
* to evaluate and execute the rule.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class SpELRule extends BasicRule {
private Condition condition = Condition.FALSE;
private final List<Action> actions = new ArrayList<>();
private final ParserContext parserContext;
private BeanResolver beanResolver;
/**
* Create a new SpEL rule.
*/
public SpELRule() {
this(ParserContext.TEMPLATE_EXPRESSION);
}
/**
* Create a new SpEL rule.
*
* @param parserContext used when parsing expressions
*/
public SpELRule(ParserContext parserContext) {
super(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
this.parserContext = parserContext;
}
/**
* Create a new SpEL rule.
*
* @param beanResolver used to resolve bean references in expressions
*/
public SpELRule(BeanResolver beanResolver) {
super(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
this.parserContext = ParserContext.TEMPLATE_EXPRESSION;
this.beanResolver = beanResolver;
}
/**
* Create a new SpEL rule.
*
* @param parserContext used when parsing expressions
* @param beanResolver used to resolve bean references in expressions
*/
public SpELRule(ParserContext parserContext, BeanResolver beanResolver) {
super(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
this.parserContext = parserContext;
this.beanResolver = beanResolver;
}
/**
* Set rule name.
*
* @param name of the rule
* @return this rule
*/
public SpELRule name(String name) {
this.name = name;
return this;
}
/**
* Set rule description.
*
* @param description of the rule
* @return this rule
*/
public SpELRule description(String description) {
this.description = description;
return this;
}
/**
* Set rule priority.
*
* @param priority of the rule
* @return this rule
*/
public SpELRule priority(int priority) {
this.priority = priority;
return this;
}
/**
* Specify the rule's condition as SpEL expression.
* @param condition of the rule
* @return this rule
*/
public SpELRule when(String condition) {
this.condition = new SpELCondition(condition, parserContext, beanResolver);
return this;
}
/**
* Add an action specified as an SpEL expression to the rule.
* @param action to add to the rule
* @return this rule
*/
public SpELRule then(String action) {
this.actions.add(new SpELAction(action, parserContext, beanResolver));
return this;
}
@Override
public boolean evaluate(Facts facts) {
return condition.evaluate(facts);
}
@Override
public void execute(Facts facts) throws Exception {
for (Action action : actions) {
action.execute(facts);
}
}
}
如上,在SpELRule表达式实现中,支持SpEL表达式定义的Condition和Action,并分别实现了规则条件的解析和规则动作的执行;
3.规则工厂类
上述对json、yml 文件脚本进行解析并完成具体规则的创建都是通过规则工厂类来完成的;
规则工厂类图如下:
首先看一下AbstractRuleFactory源码:
/**
* Base class for rule factories.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public abstract class AbstractRuleFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRuleFactory.class);
private static final List<String> ALLOWED_COMPOSITE_RULE_TYPES = Arrays.asList(
UnitRuleGroup.class.getSimpleName(),
ConditionalRuleGroup.class.getSimpleName(),
ActivationRuleGroup.class.getSimpleName()
);
protected Rule createRule(RuleDefinition ruleDefinition) {
if (ruleDefinition.isCompositeRule()) {
return createCompositeRule(ruleDefinition);
} else {
return createSimpleRule(ruleDefinition);
}
}
protected abstract Rule createSimpleRule(RuleDefinition ruleDefinition);
protected Rule createCompositeRule(RuleDefinition ruleDefinition) {
if (ruleDefinition.getCondition() != null) {
LOGGER.warn(
"Condition '{}' in composite rule '{}' of type {} will be ignored.",
ruleDefinition.getCondition(),
ruleDefinition.getName(),
ruleDefinition.getCompositeRuleType());
}
if (ruleDefinition.getActions() != null && !ruleDefinition.getActions().isEmpty()) {
LOGGER.warn(
"Actions '{}' in composite rule '{}' of type {} will be ignored.",
ruleDefinition.getActions(),
ruleDefinition.getName(),
ruleDefinition.getCompositeRuleType());
}
CompositeRule compositeRule;
String name = ruleDefinition.getName();
switch (ruleDefinition.getCompositeRuleType()) {
case "UnitRuleGroup":
compositeRule = new UnitRuleGroup(name);
break;
case "ActivationRuleGroup":
compositeRule = new ActivationRuleGroup(name);
break;
case "ConditionalRuleGroup":
compositeRule = new ConditionalRuleGroup(name);
break;
default:
throw new IllegalArgumentException("Invalid composite rule type, must be one of " + ALLOWED_COMPOSITE_RULE_TYPES);
}
compositeRule.setDescription(ruleDefinition.getDescription());
compositeRule.setPriority(ruleDefinition.getPriority());
for (RuleDefinition composingRuleDefinition : ruleDefinition.getComposingRules()) {
compositeRule.addRule(createRule(composingRuleDefinition));
}
return compositeRule;
}
}
在上述源码中,分别通过方法createSimpleRule和方法createCompositeRule完成单一规则和组合规则的创建;
下面分别从单一规则创建和组合规则创建的角度分析工厂类具体的创建过程;
在分析工厂类创建逻辑之前,我们注意到工厂类的创建方法参数是RuleDefinition,首先看一下RuleDefinition的构造;
3.1 RuleDefinition类
RuleDefinition表示规则描述类,封装了规则的名称、描述、优先级以及包含的条件和执行的动作等;
RuleDefinition的定义如下:
/**
* Rule definition as defined in a rule descriptor.
* This class encapsulates the static definition of a {@link Rule}.
*
* Rule definitions are produced by a {@code RuleDefinitionReader}s
* and consumed by rule factories to create rules.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class RuleDefinition {
private String name = Rule.DEFAULT_NAME;
private String description = Rule.DEFAULT_DESCRIPTION;
private int priority = Rule.DEFAULT_PRIORITY;
private String condition;
private List<String> actions = new ArrayList<>();
private List<RuleDefinition> composingRules = new ArrayList<>();
private String compositeRuleType;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public List<String> getActions() {
return actions;
}
public void setActions(List<String> actions) {
this.actions = actions;
}
public void setComposingRules(List<RuleDefinition> composingRuleDefinitions) {
this.composingRules = composingRuleDefinitions;
}
public void setCompositeRuleType(String compositeRuleType) {
this.compositeRuleType = compositeRuleType;
}
public String getCompositeRuleType() {
return compositeRuleType;
}
public List<RuleDefinition> getComposingRules() {
return composingRules;
}
public boolean isCompositeRule() {
return !composingRules.isEmpty();
}
}
RuleDefinition的构造是通过RuleDefinitionReader完成的,RuleDefinitionReader的定义如下:
/**
* Strategy interface for {@link RuleDefinition} readers.
*
* @see JsonRuleDefinitionReader
* @see YamlRuleDefinitionReader
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@FunctionalInterface
public interface RuleDefinitionReader {
/**
* Read a list of rule definitions from a rule descriptor.
*
* <strong> The descriptor is expected to contain a collection of rule definitions
* even for a single rule.</strong>
*
* @param reader of the rules descriptor
* @return a list of rule definitions
* @throws Exception if a problem occurs during rule definition reading
*/
List<RuleDefinition> read(Reader reader) throws Exception;
}
read方法接受Reader输入,并返回解析结果RuleDefinition列表;
EasyRule框架源码中,提供了分别针对.json文件和.yml文件的Reader实现类:
JsonRuleDefinitionReader:完成.json文件的解析,并构造RuleDefinition
YamlRuleDefinitionReader:完成.yml文件的解析,并构造RuleDefinition
RuleDefinitionReader的整体类图如下:
具体的解析逻辑封装到了抽象类接口中:
/**
* Base class for {@link RuleDefinitionReader}s.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public abstract class AbstractRuleDefinitionReader implements RuleDefinitionReader {
public List<RuleDefinition> read(Reader reader) throws Exception {
List<RuleDefinition> ruleDefinitions = new ArrayList<>();
Iterable<Map<String, Object>> rules = loadRules(reader);
for (Map<String, Object> rule : rules) {
ruleDefinitions.add(createRuleDefinition(rule));
}
return ruleDefinitions;
}
/**
* Load rules from the given reader as an iterable of Maps.
*
* @param reader to read rules from
* @return an iterable of rule Maps
* @throws Exception if unable to load rules
*/
protected abstract Iterable<Map<String, Object>> loadRules(Reader reader) throws Exception;
/**
* Create a rule definition.
*
* @param map of rule properties
* @return a rule definition
*/
protected RuleDefinition createRuleDefinition(Map<String, Object> map) {
RuleDefinition ruleDefinition = new RuleDefinition();
String name = (String) map.get("name");
ruleDefinition.setName(name != null ? name : Rule.DEFAULT_NAME);
String description = (String) map.get("description");
ruleDefinition.setDescription(description != null ? description : Rule.DEFAULT_DESCRIPTION);
Integer priority = (Integer) map.get("priority");
ruleDefinition.setPriority(priority != null ? priority : Rule.DEFAULT_PRIORITY);
String compositeRuleType = (String) map.get("compositeRuleType");
String condition = (String) map.get("condition");
if (condition == null && compositeRuleType == null) {
throw new IllegalArgumentException("The rule condition must be specified");
}
ruleDefinition.setCondition(condition);
List<String> actions = (List<String>) map.get("actions");
if ((actions == null || actions.isEmpty()) && compositeRuleType == null) {
throw new IllegalArgumentException("The rule action(s) must be specified");
}
ruleDefinition.setActions(actions);
List<Object> composingRules = (List<Object>) map.get("composingRules");
if ((composingRules != null && !composingRules.isEmpty()) && compositeRuleType == null) {
throw new IllegalArgumentException("Non-composite rules cannot have composing rules");
} else if ((composingRules == null || composingRules.isEmpty()) && compositeRuleType != null) {
throw new IllegalArgumentException("Composite rules must have composing rules specified");
} else if (composingRules != null) {
List<RuleDefinition> composingRuleDefinitions = new ArrayList<>();
for (Object rule : composingRules) {
Map<String, Object> composingRuleMap = (Map<String, Object>) rule;
composingRuleDefinitions.add(createRuleDefinition(composingRuleMap));
}
ruleDefinition.setComposingRules(composingRuleDefinitions);
ruleDefinition.setCompositeRuleType(compositeRuleType);
}
return ruleDefinition;
}
}
抽象方法loadRules交由子类具体实现,JsonRuleDefinitionReader和YamlRuleDefinitionReader的具体实现分别如下:
/**
* Rule definition reader based on <a href="https://github.com/FasterXML/jackson">Jackson</a>.
*
* This reader expects an array of rule definitions as input even for a single rule. For example:
*
* <pre>
* [{rule1}, {rule2}]
* </pre>
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@SuppressWarnings("unchecked")
public class JsonRuleDefinitionReader extends AbstractRuleDefinitionReader {
private final ObjectMapper objectMapper;
/**
* Create a new {@link JsonRuleDefinitionReader}.
*/
public JsonRuleDefinitionReader() {
this(new ObjectMapper());
}
/**
* Create a new {@link JsonRuleDefinitionReader}.
*
* @param objectMapper to use to read rule definitions
*/
public JsonRuleDefinitionReader(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
protected Iterable<Map<String, Object>> loadRules(Reader reader) throws Exception {
List<Map<String, Object>> rulesList = new ArrayList<>();
Object[] rules = objectMapper.readValue(reader, Object[].class);
for (Object rule : rules) {
rulesList.add((Map<String, Object>) rule);
}
return rulesList;
}
}
/**
* Rule definition reader based on <a href="https://github.com/FasterXML/jackson-dataformats-text/tree/master/yaml">Jackson Yaml</a>.
*
* This reader expects a collection of rule definitions as input even for a single rule. For example:
*
* <pre>
* rule1
* ---
* rule2
* </pre>
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@SuppressWarnings("unchecked")
public class YamlRuleDefinitionReader extends AbstractRuleDefinitionReader {
private final Yaml yaml;
/**
* Create a new {@link YamlRuleDefinitionReader}.
*/
public YamlRuleDefinitionReader() {
this(new Yaml());
}
/**
* Create a new {@link YamlRuleDefinitionReader}.
*
* @param yaml to use to read rule definitions
*/
public YamlRuleDefinitionReader(Yaml yaml) {
this.yaml = yaml;
}
@Override
protected Iterable<Map<String, Object>> loadRules(Reader reader) {
List<Map<String, Object>> rulesList = new ArrayList<>();
Iterable<Object> rules = yaml.loadAll(reader);
for (Object rule : rules) {
rulesList.add((Map<String, Object>) rule);
}
return rulesList;
}
}
至此,借助RuleDefinitionReader完成了RuleDefinition的解析;
3.2 组合规则创建
在方法createRule实现中,根据解析结果中是否包含组合规则会路由到不同的创建逻辑;
包含组合规则的情况下,会构造具体的组合规则,如下:
protected Rule createCompositeRule(RuleDefinition ruleDefinition) {
if (ruleDefinition.getCondition() != null) {
LOGGER.warn(
"Condition '{}' in composite rule '{}' of type {} will be ignored.",
ruleDefinition.getCondition(),
ruleDefinition.getName(),
ruleDefinition.getCompositeRuleType());
}
if (ruleDefinition.getActions() != null && !ruleDefinition.getActions().isEmpty()) {
LOGGER.warn(
"Actions '{}' in composite rule '{}' of type {} will be ignored.",
ruleDefinition.getActions(),
ruleDefinition.getName(),
ruleDefinition.getCompositeRuleType());
}
CompositeRule compositeRule;
String name = ruleDefinition.getName();
switch (ruleDefinition.getCompositeRuleType()) {
case "UnitRuleGroup":
compositeRule = new UnitRuleGroup(name);
break;
case "ActivationRuleGroup":
compositeRule = new ActivationRuleGroup(name);
break;
case "ConditionalRuleGroup":
compositeRule = new ConditionalRuleGroup(name);
break;
default:
throw new IllegalArgumentException("Invalid composite rule type, must be one of " + ALLOWED_COMPOSITE_RULE_TYPES);
}
compositeRule.setDescription(ruleDefinition.getDescription());
compositeRule.setPriority(ruleDefinition.getPriority());
for (RuleDefinition composingRuleDefinition : ruleDefinition.getComposingRules()) {
compositeRule.addRule(createRule(composingRuleDefinition));
}
return compositeRule;
}
这里实际上通过简单工厂模式完成了3种不同组合规则的创建,同时递归调用方法createRule完成了规则的递归构造,嵌套逻辑;
3.3 单一规则创建
不包含组合规则的条件下,调用createSimpleRule方法完成单一规则的创建,这里的具体实现包括:
SpELRuleFactory:创建SpEL表达式定义规则的工厂类
MVELRuleFactory:创建MVEL表达式定义规则的工厂类
JexlRuleFactory:创建Jexl表达式定义规则的工厂类
这里以SpELRuleFactory说明(其它类似),SpELRuleFactory源码如下:
/**
* Factory to create {@link SpELRule} instances.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class SpELRuleFactory extends AbstractRuleFactory {
private final RuleDefinitionReader reader;
private BeanResolver beanResolver;
private ParserContext parserContext;
/**
* Create a new {@link SpELRuleFactory} with a given reader.
*
* @param reader used to read rule definitions
* @see YamlRuleDefinitionReader
* @see JsonRuleDefinitionReader
*/
public SpELRuleFactory(RuleDefinitionReader reader) {
this(reader, ParserContext.TEMPLATE_EXPRESSION);
}
/**
* Create a new {@link SpELRuleFactory} with a given reader.
*
* @param reader used to read rule definitions
* @param parserContext used to parse SpEL expressions
* @see YamlRuleDefinitionReader
* @see JsonRuleDefinitionReader
*/
public SpELRuleFactory(RuleDefinitionReader reader, ParserContext parserContext) {
this.reader = reader;
this.parserContext = parserContext;
}
/**
* Create a new {@link SpELRuleFactory} with a given reader.
*
* @param reader used to read rule definitions
* @param beanResolver used to resolve bean references in SpEL expressions
* @see YamlRuleDefinitionReader
* @see JsonRuleDefinitionReader
*/
public SpELRuleFactory(RuleDefinitionReader reader, BeanResolver beanResolver) {
this.reader = reader;
this.beanResolver = beanResolver;
}
/**
* Create a new {@link SpELRuleFactory} with a given reader.
*
* @param reader used to read rule definitions
* @param parserContext used to parse SpEL expressions
* @param beanResolver used to resolve bean references in SpEL expressions
* @see YamlRuleDefinitionReader
* @see JsonRuleDefinitionReader
*/
public SpELRuleFactory(RuleDefinitionReader reader, ParserContext parserContext, BeanResolver beanResolver) {
this.reader = reader;
this.parserContext = parserContext;
this.beanResolver = beanResolver;
}
/**
* Create a new {@link SpELRule} from a Reader.
*
* The rule descriptor should contain a single rule definition.
* If no rule definitions are found, a {@link IllegalArgumentException} will be thrown.
* If more than a rule is defined in the descriptor, the first rule will be returned.
*
* @param ruleDescriptor descriptor of rule definition
* @return a new rule
* @throws Exception if unable to create the rule from the descriptor
*/
public Rule createRule(Reader ruleDescriptor) throws Exception {
List<RuleDefinition> ruleDefinitions = reader.read(ruleDescriptor);
if (ruleDefinitions.isEmpty()) {
throw new IllegalArgumentException("rule descriptor is empty");
}
return createRule(ruleDefinitions.get(0));
}
/**
* Create a set of {@link SpELRule} from a Reader.
*
* @param rulesDescriptor descriptor of rule definitions
* @return a set of rules
* @throws Exception if unable to create rules from the descriptor
*/
public Rules createRules(Reader rulesDescriptor) throws Exception {
Rules rules = new Rules();
List<RuleDefinition> ruleDefinitions = reader.read(rulesDescriptor);
for (RuleDefinition ruleDefinition : ruleDefinitions) {
rules.register(createRule(ruleDefinition));
}
return rules;
}
protected Rule createSimpleRule(RuleDefinition ruleDefinition) {
SpELRule spELRule = new SpELRule(parserContext, beanResolver)
.name(ruleDefinition.getName())
.description(ruleDefinition.getDescription())
.priority(ruleDefinition.getPriority())
.when(ruleDefinition.getCondition());
for (String action : ruleDefinition.getActions()) {
spELRule.then(action);
}
return spELRule;
}
}
上述createSimpleRule方法的实现中,构造了SpELRule的具体实例,完成了单一规则的创建;
至此,EasyRule框架通过工厂方法模式,完成了文件脚本规则(.json、.yml)的创建。
附工厂方法模式UML类图: