1. Overview
1.概述
Using a rule engine is a great way to separate the business logic from our boilerplate code and protecting our application code from business changes.
使用规则引擎是将业务逻辑与我们的模板代码分开的一个好方法,并保护我们的应用代码不受业务变化的影响。
In a previous article on Java Rule Engines, we mentioned the JSR 94 specification. The Jess Rule Engine holds particular importance as the reference rules driver implementation for JSR 94, so let’s take a look at it.
在上一篇关于Java规则引擎的文章中,我们提到了JSR 94规范。Jess规则引擎具有特别重要的意义 作为JSR 94的参考规则驱动实现,所以我们来看看它。
2. Jess Rule Engine
2.杰斯规则引擎
Jess is one of the earliest rule engines to be easily integrated with Java. Jess uses an enhanced implementation of the highly efficient Rete algorithm, making it much faster than a simple Java loop for most scenarios.
Jess是最早的规则引擎之一,可轻松与Java集成。Jess使用了高效的Rete算法的增强实现,使其在大多数情况下比简单的Java循环快得多。
Rules can be executed from rulesets written in the native Jess Rules Language, an extended Lisp-based syntax, or from a more verbose XML format. We’ll use the native format.
规则可以从以本地Jess规则语言(一种基于Lisp的扩展语法)编写的规则集中执行,也可以从更粗略的XML格式中执行。我们将使用本地格式。
There’s an Eclipse-based IDE for development (for older versions of Eclipse) and some excellent documentation on using and integrating Jess with Java. There’s even a REPL command-line interface where we can try out our ideas before creating a rules file.
有一个用于开发的基于Eclipse的IDE(适用于Eclipse的旧版本)和一些关于使用Jess并将其与Java集成的优秀文档。甚至还有一个 REPL 命令行界面,我们可以在创建规则文件之前尝试我们的想法。
As the reference rule engine for JSR 94, Jess is by definition JSR 94 compliant, although it’s no longer under active development.
作为JSR 94的参考规则引擎,Jess在定义上是符合JSR 94的,尽管它不再处于积极的开发中。
2.1. A Quick Word About JSR 94
2.1.关于JSR 94的简要说明
JSR 94 provides an API that we can use to give us independence from whichever rule engine we choose. We can plug any JSR 94 compliant rule engine into our code and run some rules without needing to change the way we interact with the rule engine in our application.
JSR 94提供了一个API,我们可以用它来让我们独立于我们选择的任何一个规则引擎。我们可以将任何符合JSR 94的规则引擎插入我们的代码中,并运行一些规则,而不需要改变我们在应用程序中与规则引擎的交互方式。
This doesn’t mean the rule engine’s underlying rules will look the same – we may have to rewrite those if we change the rule engine, but it does mean we won’t need to rewrite parts of our application to use the new rule engine. The only code changes we’ll need are to update the name of the driver and some rule file names.
这并不意味着规则引擎的底层规则将看起来是一样的–如果我们改变了规则引擎,我们可能必须重写这些规则,但这确实意味着我们不需要重写我们应用程序的部分内容来使用新的规则引擎。我们唯一需要修改的代码是更新驱动程序的名称和一些规则文件的名称。
2.2. The Jess JSR 94 Driver
2.2.Jess JSR 94驱动程序
Although there’s a reference rule engine driver for Jess included for JSR 94, Jess itself is not included, as it’s a licensed commercial product. The reference driver comes in the org.jcp.jsr94.jess package, but a newer driver is available in the jess.jsr94 package when we download Jess.
虽然JSR 94中包含了Jess的参考规则引擎driver ,但Jess本身并不包括在内,因为它是一个获得许可的商业产品。参考驱动程序在org.jcp.jsr94.jess包中,但是当我们下载Jess时,在jess.jsr94包中有一个更新的驱动程序。
Let’s start by looking at Jess’s native Java integration before we move on to see how the JSR 94 layer changes this.
让我们先看看Jess的本地Java集成,然后再看看JSR 94层如何改变这一点。
3. Provided Examples
3.提供的例子
Before we start integrating Jess to our code, let’s make sure we’ve downloaded it and made it available on our classpath. We’ll need to register for the free 30-day trial download unless we already have a license.
在我们开始将Jess集成到我们的代码中之前,让我们确保我们已经下载了它并使它在我们的classpath上可用。我们需要注册30天的免费试用下载,除非我们已经有了许可证。
So, let’s download Jess, unpack the downloaded Jess71p2.jar, and run one of its examples to make sure we have a working version.
因此,让我们下载Jess,解压下载的Jess71p2.jar,并运行其中的一个例子以确保我们有一个工作版本。
3.1. Standalone Jess
3.1 独立的杰斯
Let’s look in the Jess71p2/examples directory, where the jess directory holds some example rulesets. The pricing_engine directory shows an integration that can be executed via an ant build.xml script. Let’s change our directory to the pricing engine example and run the program via ant test:
让我们看看Jess71p2/examples目录,其中jess目录存放着一些规则集的例子。pricing_engine目录显示了一个可以通过ant build.xml脚本执行的集成。让我们把目录改为定价引擎示例,通过ant test运行程序。
cd Jess71p2/examples/pricing_engine
ant test
This builds and runs an example pricing ruleset:
这将建立并运行一个定价规则集的例子。
Buildfile: Jess71p2\examples\pricing_engine\build.xml
...
test:
[java] Items for order 123:
[java] 1 CD Writer: 199.99
...
[java] Items for order 666:
[java] 1 Incredibles DVD: 29.99
[java] Offers for order 666:
[java] BUILD SUCCESSFUL
Total time: 1 second
3.2. Jess With JSR 94
3.2.杰斯与JSR 94
Now that we have Jess working, let’s download JSR 94 and then unzip it to create a jsr94-1.0 directory with ant, doc, lib, and src directories inside.
现在我们有了Jess的工作,让我们下载JSR 94,然后解压缩,创建一个jsr94-1.0目录,里面有ant、doc、lib和src目录。
unzip jreng-1_0a-fr-spec-api.zip
This gives us the JSR 94 API and Jess reference driver, but it doesn’t come with the licensed Jess implementation, so if we try running an example now, we’ll get the following error:
这给了我们JSR 94 API和Jess参考驱动,但它并没有附带许可的Jess实现,所以如果我们现在尝试运行一个例子,我们会得到以下错误。
Error: The reference implementation Jess could not be found.
So, let’s add the Jess reference implementation, jess.jar, that came as part of the Jess71p2 we downloaded earlier and copy it to the JSR 94 lib directory, then run the example:
因此,让我们添加Jess参考实现,jess.jar,它作为我们之前下载的Jess71p2的一部分,并将其复制到JSR 94 lib目录下,然后运行该例子。
cp Jess71p2/lib/jess.jar jsr94-1.0/lib/
java -jar jsr94-1.0/lib/jsr94-example.jar
The example runs some rules to determine a customer’s remaining credit as invoices are paid:
这个例子运行一些规则来确定客户的剩余信用,因为发票被支付。
Administration API Acquired RuleAdministrator: org.jcp.jsr94.jess.RuleAdministratorImpl@63947c6b
...
Runtime API Acquired RuleRuntime: org.jcp.jsr94.jess.RuleRuntimeImpl@68fb2c38
Customer credit limit result: 3000
...
Invoice 2 amount: 1750 status: paid
Released Stateful Rule Session.
4. Integrating Jess With Java
4.将Jess与Java结合起来
Now that we have Jess and JSR 94 downloaded and have run some rules both natively and via the JSR, let’s look at how to integrate a Jess ruleset into a Java program.
现在我们已经下载了Jess和JSR 94,并在本地和通过JSR运行了一些规则,让我们看看如何将Jess规则集集成到Java程序中。
In our example, we’ll start by executing a simple Jess rules file, hellojess.clp, from Java code, and then look at another rules file, bonus.clp, that will use and modify some of our objects.
在我们的例子中,我们将从Java代码开始执行一个简单的Jess规则文件,hellojess.clp,,然后再看另一个规则文件,bonus.clp,它将使用和修改我们的一些对象。
4.1. Maven Dependency
4.1.Maven的依赖性
There’s no Maven dependency available for Jess, so if we haven’t already done so, let’s download and unpack the Jess jar (jess.jar) and mvn install it to our local Maven repository:
Jess没有Maven依赖性,所以如果我们还没有这样做,让我们下载并解压Jess jar(jess.jar),mvn install它到我们本地的Maven仓库。
mvn install:install-file -Dfile=jess.jar -DgroupId=gov.sandia -DartifactId=jess -Dversion=7.1p2 -Dpackaging=jar -DgeneratePom=true
We can then add it as a dependency in the usual way:
然后我们可以用通常的方式将其作为依赖关系添加。
<dependency>
<groupId>gov.sandia</groupId>
<artifactId>jess</artifactId>
<version>7.1p2</version>
</dependency>
4.2. Hello Jess Rules File
4.2.Hello Jess规则文件
Next, let’s create the simplest of rules files to print out a message. We’ll save the rules file as hellojess.clp:
接下来,让我们创建一个最简单的规则文件来打印出一条信息。我们将规则文件保存为hellojess.clp。
(printout t "Hello from Jess!" crlf)
4.3. Jess Rule Engine
4.3.杰斯规则引擎
Now, let’s create an instance of the Jess Rete rule engine, reset() it to its initial state, load up the rules in hellojess.clp, and run them:
现在,让我们创建一个Jess Rete 规则引擎的实例,reset()它的初始状态,加载hellojess.clp中的规则,并运行它们。
public class HelloJess {
public static void main(String[] args) throws JessException {
Rete engine = new Rete();
engine.reset();
engine.batch("hellojess.clp");
engine.run();
}
For this simple example, we’ve just added the potential JessException to our main method’s throws clause.
在这个简单的例子中,我们只是将潜在的JessException添加到我们的mainmethod的throws子句。
When we run our program, we’ll see the output:
当我们运行我们的程序时,我们会看到输出。
Hello from Jess!
5. Integrating Jess to Java With Data
5.用数据整合Jess to Java
Now that everything is installed correctly and we can run rules, let’s see how we add data for the rule engine to process and how we retrieve the results.
现在一切都已正确安装,我们可以运行规则了,让我们看看我们如何为规则引擎添加数据来处理,以及我们如何检索结果。
First, we’ll need some Java classes to work with, and then a new ruleset that uses them.
首先,我们需要一些Java类来工作,然后需要一个使用它们的新规则集。
5.1. Model
5.1 模式
Let’s create some simple Question and Answer classes:
让我们创建一些简单的Question和Answer类。
public class Question {
private String question;
private int balance;
// getters and setters
public Question(String question, int balance) {
this.question = question;
this.balance = balance;
}
}
public class Answer {
private String answer;
private int newBalance;
// getters and setters
public Answer(String answer, int newBalance) {
this.answer = answer;
this.newBalance = newBalance;
}
}
5.2. Jess Rule With Input and Output
5.2.带输入和输出的Jess规则
Now, let’s create a simple Jess ruleset called bonus.clp that we’ll pass a Question to and receive an Answer from.
现在,让我们创建一个简单的Jess规则集,名为bonus.clp,我们将把一个Question传递给它,并从它那里接收一个Answer。
First, we import our Question and Answer classes and then use Jess’s deftemplate function to make them available to the rule engine:
首先,我们导入我们的Question和Answer类,然后使用Jess的deftemplate函数来使它们对规则引擎可用。
(import com.baeldung.rules.jsr94.jess.model.*)
(deftemplate Question (declare (from-class Question)))
(deftemplate Answer (declare (from-class Answer)))
Note the use of parentheses, which denote Jess function calls.
注意圆括号的使用,它表示Jess函数调用。
Now, let’s use defrule to add a single rule avoid-overdraft in Jess’s extended Lisp format that gives us a bonus of $50 if the balance in our Question is below zero:
现在,让我们用defrule添加一条Jess的扩展Lisp格式的avoid-overdraft规则,如果我们的Question中的余额低于0,就给我们50美元的奖金。
(defrule avoid-overdraft "Give $50 to anyone overdrawn"
?q <- (Question { balance < 0 })
=>
(add (new Answer "Overdrawn bonus" (+ ?q.balance 50))))
Here, the “?” binds an object to a variable q when the conditions on the right-hand side of the “<-“ match. In this case, that’s when the rule engine finds a Question that has a balance less than 0.
在这里,当”<-“右侧的条件匹配时,”?”将一个对象绑定到一个变量q上。在这种情况下,就是当规则引擎发现一个Question的平衡度小于0的时候。
When it does, then the actions to the right of the “=>” are triggered so the engine adds a new Answer object to the working memory. We give it the two required constructor arguments: “Overdrawn bonus” for the answer parameter and a (+) function to calculate the newAmount parameter.
当它这样做时,”=>”右边的动作被触发,所以引擎添加一个新的答案对象到工作存储器。我们给它两个必要的构造器参数。”透支的奖金 “作为答案参数,以及一个(+)函数来计算新金额参数。
5.3. Manipulating Data With the Jess Rule Engine
5.3.用Jess规则引擎操纵数据
We can use add() to add a single object at a time to our rule engine’s working memory, or addAll() to add a collection of data. Let’s use add() to add a single question:
我们可以使用add()一次向我们的规则引擎的工作内存添加单个对象,或者使用addAll()添加一个数据集合。让我们使用add()来添加一个单一的问题。
Question question = new Question("Can I have a bonus?", -5);
engine.add(data);
With all of our data in place, let’s execute our rules:
随着我们所有的数据到位,让我们执行我们的规则。
engine.run();
The Jess Rete engine will work its magic and return when all relevant rules have executed. In our case, we’ll have an Answer to inspect.
Jess Rete引擎将发挥它的魔力,在所有相关规则执行完毕后返回。在我们的例子中,我们会有一个Answer来检查。
Let’s use a jess.Filter to extract our Answer from the rule engine into an Iterable results object:
让我们使用jess.Filter从规则引擎中提取我们的Answer到一个Iterable结果对象:。
Iterator results = engine.getObjects(new jess.Filter.ByClass(Answer.class));
while (results.hasNext()) {
Answer answer = (Answer) results.next();
// process our Answer
}
We don’t have any reference data in our simple example, but when we do, we can use a WorkingMemoryMarker and engine.mark() to mark the state of the rule engine’s working memory after adding the data. Then we can call engine.resetToMark with our marker to reset the working memory to our “loaded” state and efficiently reuse the rule engine for a different set of objects:
在我们的简单示例中,我们没有任何参考数据,但是当我们有时,我们可以使用WorkingMemoryMarker和engine.mark()来标记添加数据后规则引擎的工作内存状态。然后我们可以用我们的标记调用engine.resetToMark,将工作内存重置为我们的 “加载 “状态,并为不同的对象集有效地重复使用规则引擎。
WorkingMemoryMarker marker;
// load reference data
marker = engine.mark();
// load specific data and run rules
engine.resetToMark(marker);
Now, let’s take a look at how we run this same ruleset using JSR 94.
现在,让我们来看看我们如何使用JSR 94运行这个相同的规则集。
6. Using JSR 94 to Integrate the Jess Rule Engine
6.使用JSR 94来整合Jess规则引擎
JSR 94 standardizes how our code interacts with a rule engine. This makes it easier to change our rule engine without significantly changing our application if a better alternative comes along.
JSR 94规范了我们的代码如何与规则引擎进行交互。这使得我们更容易改变我们的规则引擎,如果有更好的替代方案出现,也不需要大幅改变我们的应用程序。
The JSR 94 API comes in two main packages:
JSR 94 API有两个主要包。
- javax.rules.admin – for loading drivers and rules
- javax.rules – to run the rules and extract results
We’ll look at how to use the classes in both of these.
我们将看看如何在这两个地方使用类。
6.1. Maven Dependency
6.1.Maven依赖性
First, let’s add a Maven dependency for jsr94:
首先,让我们为jsr94添加一个Maven依赖项。
<dependency>
<groupId>jsr94</groupId>
<artifactId>jsr94</artifactId>
<version>1.1</version>
</dependency>
6.2. Administration API
6.2.管理API
To start using JSR 94, we need to instantiate a RuleServiceProvider. Let’s create one, passing it our Jess rules driver:
为了开始使用JSR 94,我们需要实例化一个RuleServiceProvider。让我们创建一个,把我们的Jess规则驱动传给它。
String RULE_SERVICE_PROVIDER="jess.jsr94";
Class.forName(RULE_SERVICE_PROVIDER + ".RuleServiceProviderImpl");
RuleServiceProvider ruleServiceProvider = RuleServiceProviderManager.getRuleServiceProvider(RULE_SERVICE_PROVIDER);
Now, let’s get Jess’s JSR 94 RuleAdministrator, load our example ruleset into a JSR 94 RuleExecutionSet, and register it for execution with a URI of our choice:
现在,让我们获取Jess的JSR 94 RuleAdministrator,将我们的示例规则集加载到JSR 94 RuleExecutionSet,中,并通过我们选择的URI来注册执行。
RuleAdministrator ruleAdministrator = serviceProvider.getRuleAdministrator();
InputStream ruleInput = JessRunner.class.getResourceAsStream(rulesFile);
HashMap vendorProperties = new HashMap();
RuleExecutionSet ruleExecutionSet = ruleAdministrator
.getLocalRuleExecutionSetProvider(vendorProperties)
.createRuleExecutionSet(ruleInput, vendorProperties);
String rulesURI = "rules://com/baeldung/rules/bonus";
ruleAdministrator.registerRuleExecutionSet(rulesURI, ruleExecutionSet, vendorProperties);
The Jess driver doesn’t need the vendorProperties map we supplied to RuleAdministrator, but it’s part of the interface, and other vendors may require it.
Jess驱动程序不需要我们提供给RuleAdministrator的vendorProperties映射,但它是接口的一部分,其他供应商可能需要它。
Now that our rule engine provider, Jess, has been initialized and our ruleset has been registered, we are almost ready to run our rules.
现在,我们的规则引擎提供者Jess已经被初始化,我们的规则集已经被注册,我们几乎已经准备好运行我们的规则。
Before we can run them, we need a runtime instance and a session to run them in. Let’s also add a placeholder, calculateResults(), for where the magic will happen, and release the session:
在我们可以运行它们之前,我们需要一个运行时实例和一个会话来运行它们。让我们也添加一个占位符,calculateResults(),,作为发生魔法的地方,并释放会话。
RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
StatelessRuleSession statelessRuleSession
= (StatelessRuleSession) ruleRuntime.createRuleSession(rulesURI, new HashMap(), RuleRuntime.STATELESS_SESSION_TYPE);
calculateResults(statelessRuleSession);
statelessRuleSession.release();
6.3. Execution API
6.3.执行API
Now that we have everything in place, let’s implement calculateResults to supply our initial data, execute our rules in a stateless session, and extract the results:
现在我们已经有了一切,让我们实现calculateResults以提供我们的初始数据,在无状态会话中执行我们的规则,并提取结果。
List data = new ArrayList();
data.add(new Question("Can I have a bonus?", -5));
List results = statelessRuleSession.executeRules(data);
Since JSR 94 was written before JDK 5 came along, the API doesn’t use generics so let’s just use an Iterator to see the results:
由于JSR 94是在JDK 5出现之前写的,API没有使用泛型,所以我们就用一个Iterator来看看结果。
Iterator itr = results.iterator();
while (itr.hasNext()) {
Object obj = itr.next();
if (obj instanceof Answer) {
int answerBalance = ((Answer) obj).getCalculatedBalance());
}
}
We’ve used a stateless session in our example, but we can also create a StatefuleRuleSession if we want to maintain state between invocations.
在我们的例子中,我们使用了一个无状态的会话,但是如果我们想在调用之间保持状态,我们也可以创建一个StatefuleRuleSession。
7. Conclusion
7.结语
In this article, we learned how to integrate the Jess rule engine into our application by using Jess’s native classes and, with a bit more effort, by using JSR 94. We’ve seen how business rules can be separated into separate files that get processed by the rule engine when our application runs.
在这篇文章中,我们学习了如何通过使用Jess的本地类,以及通过使用JSR 94,将Jess规则引擎集成到我们的应用程序中。我们已经看到了业务规则如何被分离成独立的文件,当我们的应用程序运行时,这些文件会被规则引擎处理。
If we have rules for the same business logic, written for another JSR 94 compliant rule engine, then we can simply add in the driver for our alternative rule engine, and update the driver name our application should use, and no further code changes should be necessary.
如果我们有相同业务逻辑的规则,并为另一个符合JSR 94的规则引擎编写,那么我们可以简单地添加我们的替代规则引擎的驱动程序,并更新我们的应用程序应该使用的驱动程序名称,并且不需要进一步修改代码。
There are more details at jess.sandia.gov for Embedding Jess in a Java Application, and Oracle has a useful guide for Getting Started With the Java Rule Engine API (JSR 94).
在jess.sandia.gov网站上有更多关于在Java应用程序中嵌入Jess的详细信息,而Oracle有一个有用的指南开始使用Java规则引擎API(JSR 94)。
As usual, the code we looked at in this article is available over on GitHub.
像往常一样,我们在本文中所看到的代码可以在GitHub上找到。