В последнее время я думаю о техническом рефакторинге выбора на основе правил. Я хочу реализовать его через механизм правил. Я могу воспользоваться этой возможностью, чтобы подробно узнать о механизме правил. В этой статье будет подробно рассказано об использовании механизма правил easy-rules. адрес проекта:GitHub.com/just-easy/easy…
Введение
Easy Rules — простой, но мощный механизм правил Java., который обеспечивает следующие возможности:
- Легкий фреймворк и простой в освоении API
- Разработка на основе POJO
- Поддержка создания комбинированных правил из исходных правил.
- Поддерживает определение правил с помощью таких выражений, как MVEL, SPEL и JEXL.
начать использовать
импортировать зависимости
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
Выше представлены только основные зависимости модуля,Если вам нужен другой контент модуля, вы можете импортировать соответствующие зависимости.
определить правила
вводить
Большинство бизнес-правил можно представить следующими определениями:
- name: Уникальное имя правила в пространстве имен правил.
- description: краткое описание правила
- priority: приоритет правила
- facts: набор известных фактов, когда правило срабатывает
- conditions: набор условий, которые необходимо выполнить, чтобы правило применялось, с учетом некоторых фактов.
- actions: набор действий, выполняемых при выполнении условия (может добавлять/удалять/изменять факты)
Easy Rules предоставляет абстракции для каждого ключевого момента определения бизнес-правил. Правила в Easy Rules даныRule
Представление интерфейса:
public interface Rule extends Comparable<Rule> {
/**
* 此方法封装了规则的条件。
* @return 如果根据提供的事实可以应用规则,则为true,否则为false
*/
boolean evaluate(Facts facts);
/**
* 此方法封装了规则的操作。
* @throws 如果在执行操作期间发生错误,则抛出异常
*/
void execute(Facts facts) throws Exception;
//Getters and setters for rule name, description and priority omitted.
}
evaluate()
Метод инкапсулирует должен бытьtrue
условия для срабатывания правила.execute()
Метод инкапсулирует действие, которое должно быть выполнено при соблюдении условий правила. условия и действияCondition
иAction
Представление интерфейса.
Правила можно определить двумя способами:
- в
POJO
добавить аннотации для объявления - пройти через
RuleBuilder
программирование API
Это наиболее распространенные способы определения правил, но вы также можете реализоватьRule
интерфейс или расширениеBasicRule
Добрый.
Используйте аннотации для определения правил
Простые правила обеспечивают@Rule
Аннотации, которые могут преобразовывать POJO в правила.
@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {
@Condition
public boolean when(@Fact("fact") fact) {
// 规则条件
return true;
}
@Action(order = 1)
public void then(Facts facts) throws Exception {
// 规则为true时的操作1
}
@Action(order = 2)
public void finally() throws Exception {
// 规则为true时的操作2
}
}
@Condition
Аннотация используется для обозначения метода оценки условия правила, этот метод должен бытьpublic
, который может иметь одну или несколько полос@Fact
аннотированные параметры и возвращаетboolean
тип. Есть только один способ использования@Condition
Тег аннотации.
@Action
Аннотации используются для обозначения методов для выполнения действий, а правила могут иметь несколько действий. можно использоватьorder
Свойства выполняют операции в указанном порядке.
использоватьRuleBuilder
определить правила
RuleBuilder
Позволяет вам определять правила с потоковым API.
Rule rule = new RuleBuilder()
.name("myRule")
.description("myRuleDescription")
.priority(3)
.when(condition)
.then(action1)
.then(action2)
.build();
В этом примереcondition
даCondition
экземпляр интерфейса,action1
иaction2
даAction
Экземпляр интерфейса.
Правила комбинирования
Easy Rules позволяет создавать сложные правила из простых правил. ОдинCompositeRule
Состоит из набора правил. Правила композиции — это абстрактное понятие, потому что правила композиции могут запускаться по-разному. Простые правила обеспечивают 3CompositeRule
реализация.
-
UnitRuleGroup
: Группа правил объекта представляет собой комбинацию правил, используемых как блок, при этом применяются либо все правила, либо ни одно из них. -
ActivationRuleGroup
: Активация группы правил запускает первое применимое правило и игнорирует другие правила в группе. Сначала правила сортируются в соответствии с их естественным порядком в группе (приоритет по умолчанию). -
ConditionalRuleGroup
: Группа условных правил принимает правило с наивысшим приоритетом в качестве условия, и если правило с наивысшим приоритетом оценивается как истинное, то остальные правила будут активированы.
Комбинированные правила могут быть созданы из исходных правил и зарегистрированы как обычные правила.
// 从两个原始规则创建组合规则
UnitRuleGroup myUnitRuleGroup =
new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);
// 像常规规则一样注册组合规则
Rules rules = new Rules();
rules.register(myUnitRuleGroup);
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);
приоритет правила
Каждое правило в Easy Rules имеет приоритет. Это представляет порядок по умолчанию, в котором запускаются правила регистрации. По умолчанию более низкие значения имеют более высокий приоритет. Чтобы переопределить это поведение, вы должны переопределитьcompareTo()
метод для предоставления пользовательской стратегии приоритетов.
- Если это унаследовано
BasicRule
, вы можете указать приоритет в конструкторе или переопределитьgetPriority()
метод. - Если вы используете POJO для определения правил, вы можете передать
@Rule
аннотированныйpriority
атрибут, чтобы указать приоритет, или использовать@Priority
Аннотация отмечает метод. Этот метод должен бытьpublic
, без аргументов, но с возвращаемым типомInteger
. - При использовании
RuleBuilder
Чтобы определить правила, вы можете использоватьRuleBuilder#priority()
Метод указывает приоритет.
Rules API
Набор правил в Easy rules представлен API правил. Он используется следующим образом:
Rules rules = new Rules();
rules.register(myRule1);
rules.register(myRule2);
Rules
Указывает пространство имен зарегистрированных правил, поэтому в рамках одного пространства имен каждое зарегистрированное правило должно иметь уникальное имя..
Rules
черезRule#compareTo()
методы сравниваются, поэтомуRule
Реализация должна быть реализована правильноcompareTo()
метод для обеспечения уникальных имен правил в пределах одного пробела.
определить факты
Факт в простых правилах определяется какFact
Указывает:
public class Fact<T> {
private final String name;
private final T value;
}
У факта есть имя и значение,Ни один не можетnull
. с другой стороны,Facts
API представляет набор фактов и действует как пространство имен для фактов. Это означает, что вFacts
Экземпляры, факты должны иметь уникальные имена.
Вот пример того, как определить факт:
Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);
Вы также можете использовать более короткую версию, создавая именованные факты с помощью метода put, например:
Facts facts = new Facts();
facts.put("foo", "bar");
можно использовать@Fact
Аннотации вводят факты в условия правил и методы действий.. В следующих правилахrain
Факты внедряются вitRains
методrain
Среди параметров:
@Rule
class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella(Facts facts) {
System.out.println("It rains, take an umbrella!");
// can add/remove/modify facts
}
}
ТипFacts
В параметры будут введены все известные факты.
Уведомление:
- Если введенный факт отсутствует в условном методе, движок зарегистрирует предупреждение и посчитает условную оценку как
false
. - Если внедренный факт отсутствует в методе действия, действие не выполняется и выдает
org.jeasy.rules.core.NoSuchFactException
аномальный.
Определение механизма правил
Простые правила обеспечиваютRulesEngine
Две реализации интерфейса:
-
DefaultRulesEngine
: применять правила в соответствии с их естественным порядком (по умолчанию приоритет). -
InferenceRulesEngine
: Продолжайте применять правила к известным фактам, пока не закончатся доступные правила.
Создать механизм правил
Механизм правил можно создать с помощью конструктора.
RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();
Зарегистрированные правила могут быть активированы следующим образом.
rulesEngine.fire(rules, facts);
Параметры двигателя правил
Движок Easy Rules можно настроить со следующими параметрами:
параметр | тип | По умолчанию |
---|---|---|
rulePriorityThreshold | int | MaxInt |
skipOnFirstAppliedRule | boolean | false |
rulePriorityThreshold | int | false |
skipOnFirstFailedRule | boolean | false |
skipOnFirstNonTriggeredRule | boolean | false |
-
skipOnFirstAppliedRule
: при успешном применении правила остальные правила пропускаются. -
skipOnFirstFailedRule
: если правило не выполняется, пропустите остальные правила. -
skipOnFirstNonTriggeredRule
: если правило не срабатывает, пропустите остальные правила. -
rulePriorityThreshold
: когда приоритет превышает указанный порог, остальные правила пропускаются.
можно использоватьRulesEngineParameters
API указывает следующие параметры:
RulesEngineParameters parameters = new RulesEngineParameters()
.rulePriorityThreshold(10)
.skipOnFirstAppliedRule(true)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
Если вы хотите получить параметры от вашего движка, вы можете использовать следующий фрагмент кода:
RulesEngineParameters parameters = myEngine.getParameters();
Это позволяет сбросить параметры двигателя после их создания.
Определить прослушиватель правил
в состоянии пройтиRuleListener
API для прослушивания событий выполнения правил:
public interface RuleListener {
/**
* 在评估规则之前触发。
*
* @param rule 正在被评估的规则
* @param facts 评估规则之前的已知事实
* @return 如果规则应该评估,则返回true,否则返回false
*/
default boolean beforeEvaluate(Rule rule, Facts facts) {
return true;
}
/**
* 在评估规则之后触发
*
* @param rule 评估之后的规则
* @param facts 评估规则之后的已知事实
* @param evaluationResult 评估结果
*/
default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }
/**
* 运行时异常导致条件评估错误时触发
*
* @param rule 评估之后的规则
* @param facts 评估时的已知事实
* @param exception 条件评估时发生的异常
*/
default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }
/**
* 在规则操作执行之前触发。
*
* @param rule 当前的规则
* @param facts 执行规则操作时的已知事实
*/
default void beforeExecute(Rule rule, Facts facts) { }
/**
* 在规则操作成功执行之后触发
*
* @param rule t当前的规则
* @param facts 执行规则操作时的已知事实
*/
default void onSuccess(Rule rule, Facts facts) { }
/**
* 在规则操作执行失败时触发
*
* @param rule 当前的规则
* @param facts 执行规则操作时的已知事实
* @param exception 执行规则操作时发生的异常
*/
default void onFailure(Rule rule, Facts facts, Exception exception) { }
}
Этот интерфейс может быть реализован для обеспечения пользовательского поведения для выполнения до/после каждого правила. Чтобы зарегистрировать прослушиватель, используйте следующий фрагмент кода:
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRuleListener(myRuleListener);
Можно зарегистрировать любое количество слушателей, и они будут выполняться в том порядке, в котором были зарегистрированы.
Примечание. При использовании правил композиции слушатели вызываются вокруг правил композиции.
Определение обработчиков правил
в состоянии пройтиRulesEngineListener
API для мониторинга событий выполнения механизма правил:
public interface RulesEngineListener {
/**
* 在执行规则集之前触发
*
* @param rules 要触发的规则集
* @param facts 触发规则前的事实
*/
default void beforeEvaluate(Rules rules, Facts facts) { }
/**
* 在执行规则集之后触发
*
* @param rules 要触发的规则集
* @param facts 触发规则前的事实
*/
default void afterExecute(Rules rules, Facts facts) { }
}
RulesEngineListener
Позволяет нам обеспечить пользовательское поведение до/после запуска всего набора правил. Слушатели могут быть зарегистрированы следующим образом.
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRulesEngineListener(myRulesEngineListener);
Можно зарегистрировать любое количество слушателей, и они будут выполняться в том порядке, в котором были зарегистрированы.
Поддержка языка выражений (EL)
Поддержка простых правилMVEL
,SpEL
иJEXL
Определите правила.
Примечания поставщика EL
У провайдеров EL есть некоторые различия в поведении. Например, если в условии отсутствует факт,MVEL
выдает исключение, аSpEL
Он будет проигнорирован и будет возвращено значение false. Поэтому вы должны понимать эти различия, прежде чем выбирать, какой EL использовать с Easy Rules.
Определение правил программно
Условия, действия и правила определяютсяMVELCondition/SpELCondition/JexlCondition
,MVELAction/SpELAction/JexlAction
иMVELRule/SpELRule/JexlRule
представительство класса. Ниже приведено использованиеMVEL
Примеры определения правил:
Rule ageRule = new MVELRule()
.name("age rule")
.description("Check if person's age is > 18 and marks the person as adult")
.priority(1)
.when("person.age > 18")
.then("person.setAdult(true);");
Определение правил через файл описания правил
Правила можно определить с помощью файла описания правил, используяMVELRuleFactory
/SpELRuleFactory
/JexlRuleFactory
для создания правила из файла дескриптора. Ниже приведенalcohol-rule.yml
Китай и ИзраильYAML
формат определенMVEL
Пример правила:
name: "alcohol rule"
description: "children are not allowed to buy alcohol"
priority: 2
condition: "person.isAdult() == false"
actions:
- "System.out.println("Shop: Sorry, you are not allowed to buy alcohol");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
MVELRule alcoholRule = ruleFactory.createRule(new FileReader("alcohol-rule.yml"));
Вы также можете создать несколько правил в одном файле.
---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:
- "person.setAdult(true);"
---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:
- "System.out.println("It rains, take an umbrella!");"
Эти правила можно загрузить вrules
в объекте.
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));
Easy Rules также поддерживает загрузку правил из дескрипторов JSON. Конкретные справочные документы здесь не раскрываются.
Обработка ошибок в определениях правил
Поведение движка в отношении некорректных выражений в условиях
Для любых исключений во время выполнения, которые могут возникнуть во время оценки условия (отсутствующие факты, опечатки в выражениях и т. д.), движок регистрирует предупреждение и рассматривает условие, оцененное какfalse
. можно использоватьRuleListener#onEvaluationError
слушать ошибки оценки.
Поведение движка в отношении некорректных выражений в операциях
Для любых исключений во время выполнения, которые могут возникнуть при выполнении операции (отсутствующие факты, опечатки в выражениях и т. д.), операция не будет выполнена, и движок зарегистрирует ошибку. можно использоватьRuleListener#onFailure
для прослушивания исключений выполнения операции. Когда правило не выполняется, механизм переходит к следующему правилу, если оно не установлено.skipOnFirstFailedRule
параметр.
настоящие каштаны
Этот каштан использует простые правила для реализации приложения FizzBuzz. FizzBuzz — это простое приложение, которое должно считать от 1 до 100 и:
- Если число кратно 5, выведите «fizz».
- Если число кратно 7, выведите «buzz».
- Если число кратно 5 и 7, выведите «fizzbuzz».
- иначе напечатайте сам номер
public class FizzBuzz {
public static void main(String[] args) {
for(int i = 1; i <= 100; i++) {
if (((i % 5) == 0) && ((i % 7) == 0))
System.out.print("fizzbuzz");
else if ((i % 5) == 0) System.out.print("fizz");
else if ((i % 7) == 0) System.out.print("buzz");
else System.out.print(i);
System.out.println();
}
System.out.println();
}
}
Напишем правило для каждого требования:
@Rule
public class FizzRule {
@Condition
public boolean isFizz(@Fact("number") Integer number) {
return number % 5 == 0;
}
@Action
public void printFizz() {
System.out.print("fizz");
}
@Priority
public int getPriority() {
return 1;
}
}
@Rule
public class BuzzRule {
@Condition
public boolean isBuzz(@Fact("number") Integer number) {
return number % 7 == 0;
}
@Action
public void printBuzz() {
System.out.print("buzz");
}
@Priority
public int getPriority() {
return 2;
}
}
public class FizzBuzzRule extends UnitRuleGroup {
public FizzBuzzRule(Object... rules) {
for (Object rule : rules) {
addRule(rule);
}
}
@Override
public int getPriority() {
return 0;
}
}
@Rule
public class NonFizzBuzzRule {
@Condition
public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
return number % 5 != 0 || number % 7 != 0;
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.print(number);
}
@Priority
public int getPriority() {
return 3;
}
}
Вот некоторые пояснения к этим правилам:
-
FizzRule
иBuzzRule
Просто они проверяют, является ли ввод кратным 5 или кратным 7, и печатают результат. -
FizzBuzzRule
является комбинационным правилом. пройти черезFizzRule
иBuzzRule
Создайте. Базовый класс выбирается какUnitRuleGroup
, либо выполняются и применяются оба правила, либо ничего не применяется. -
NonFizzBuzzRule
правило, когда оно не кратно ни 5, ни кратно 7.
Обратите внимание, что мы установили приоритет, чтобы правила срабатывали в том же порядке, что и в примере с Java.
Затем мы должны зарегистрировать эти правила в наборе правил и использовать механизм правил для их запуска:
public class FizzBuzzWithEasyRules {
public static void main(String[] args) {
// 创建规则引擎
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// 创建规则
Rules rules = new Rules();
rules.register(new FizzRule());
rules.register(new BuzzRule());
rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
rules.register(new NonFizzBuzzRule());
// 触发规则
Facts facts = new Facts();
for (int i = 1; i <= 100; i++) {
facts.put("number", i);
fizzBuzzEngine.fire(rules, facts);
System.out.println();
}
}
}
Обратите внимание, что мы установилиskipOnFirstAppliedRule
параметр для пропуска последующих правил при успешном применении правила.