JEXL 是一个表达式语言引擎,可以在应用程序或框架中实现动态和脚本功能。先写一个简单的例子,最直观的感受下。

依赖

首先我们是引入依赖包,添加pom文件中的dependency

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl3</artifactId>
    <version>3.1</version>
</dependency>

基础用法

再新建一个测试用的对象类

public class TestJexlObj {
    private int numA;
    private int numB;

    //get.set方法省略
}

之后我们来写一份测试逻辑用的代码

@Test
public void test() {
    TestJexlObj testObj = new TestJexlObj();
    testObj.setNumA(12);
    testObj.setNumB(9);
    JexlEngine jexl = new Engine();
    JexlExpression expression = jexl.createExpression("'numA is '+testObj.getNumA()+' numB is '+testObj.getNumB()");
    JexlContext jc = new MapContext();
    jc.set("testObj", testObj);
    String str = String.valueOf(expression.evaluate(jc));
    System.out.println(str);
    JexlExpression expression2 = jexl.createExpression("'numA + numB = '+(testObj.getNumA()+testObj.getNumB())");
    String str2 = String.valueOf(expression2.evaluate(jc));
    System.out.println(str2);
}

执行后我们可以看到如下结果

以及一份多行构建java对象的表达式作为Script执行的示例代码

JexlContext jc = new MapContext();
jc.set("TestJexlObjClass",TestJexlObj.class);
JexlScript expression3 = jexl.createScript(
        "testObj2 = new (TestJexlObjClass);" +
        "testObj2.setNumA(1919);"+
        "testObj2.setNumB(54);" +
        "return testObj2;");
TestJexlObj obj2 = (TestJexlObj)expression3.execute(jc);
System.out.println(obj2.getNumA()+", "+obj2.getNumB());

可以得到如下结果

表达式默认支持一些操作,如下

OperatorMethod NameExample
+addadd(x, y)
subtractsubtract(x, y)
*multiplymultiply(x, y)
/dividedivide(x, y)
%modmod(x, y)
&bitwiseAndbitwiseAnd(x, y)
|bitwiseOrbitwiseOr(x, y)
^bitwiseXorbitwiseXor(x, y)
!logicalNotlogicalNot(x)
bitwiseComplementbitiwiseComplement(x)
==equalsequals(x, y)
<lessThanlessThan(x, y)
<=lessThanOrEquallessThanOrEqual(x, y)
>greaterThangreaterThan(x, y)
>=greaterThanOrEqualgreaterThanOrEqual(x, y)
negatenegate(x)
sizesizesize(x)
emptyemptyempty(x)

自定义扩展函数

如果上述的这些操作不满足你的需求,或者你想要更加简洁地封装起你的表达式逻辑,则可以通过下面的方法,将自己的类注册为命名空间来扩展JEXL脚本,来实现更加复杂的逻辑

新建一个包含你逻辑方法的类

public static class IncClass{
        public void inc(TestIncObj testIncObj){
            testIncObj.num++;
        }
}

以及你可能需要用到的对象

public class TestIncObj {
    public int num = 0;
}

之后来实现主要逻辑

public void funcTest(){
    Map<String, Object> funcs = new HashMap<>();
    funcs.put("IncClass", new IncClass());
    JexlEngine jexlInc = new JexlBuilder().namespaces(funcs).create();

    TestIncObj testIncObj = new TestIncObj();
    testIncObj.num = 0;
    JexlContext jc = new MapContext();
    jc.set("testIncObj", testIncObj);

    JexlExpression e = jexlInc.createExpression("IncClass:inc(testIncObj)");
    e.evaluate(jc);

    System.out.println(testIncObj.num);
}

这段代码我们可以看到,将IncClass类加入进Jexl的命名空间,这样我们就可以调用IncClass自带的inc方法。通过inc方法的调用来实现对TestIncObj对象的num成员变量进行++操作。执行代码可以看到结果输出为1,表示确实num变量被执行了++操作。

关于线程安全

简单写个demo看下

public void test2() {

    TestJexlObj testObj = new TestJexlObj();
    testObj.setNumA(0);
    CountDownLatch countDownLatch = new CountDownLatch(2);

    final int[] num = {0};
    AtomicInteger integer = new AtomicInteger(0);

    JexlEngine jexl = new Engine();
    JexlContext jc = new MapContext();
    jc.set("testObj", testObj);
    JexlExpression expression = jexl.createExpression("testObj.incNumA()");
    Thread thread1 = new Thread(() -> {
        for (int i = 0; i < 100000; i++) {
            num[0]++;
            expression.evaluate(jc);
            integer.getAndIncrement();
        }
        countDownLatch.countDown();
    });
    Thread thread2 = new Thread(() -> {
        for (int i = 0; i < 100000; i++) {
            num[0]++;
            expression.evaluate(jc);
            integer.getAndIncrement();
        }
        countDownLatch.countDown();
    });
    thread1.start();
    thread2.start();
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(testObj.getNumA());
    System.out.println(num[0]);
    System.out.println(integer.get());
}
public class TestJexlObj {
    private int numA;

    public int getNumA() {
        return numA;
    }

    public int incNumA(){
        return numA++;
    }

    public void setNumA(int numA) {
        this.numA = numA;
    }
}

可以很直观的看到执行出来的结果JexlExpression在执行evaluate的时候不会去保证原子性,和正常的方法调用的时候是一样的,需要开发人员自行保证原子性。

官方文档中有这么一段描述

Both JexlEngine and JxltEngine are thread-safe, most of their inner fields are final; the same instance can be shared between different threads and proper synchronization is enforced in critical areas (introspection caches).

大意是指JexlEngine和JxltEngine都是线程安全的,它们的大部分内部字段都是final;同一实例可以在不同线程之间共享,并且在关键区域(内省缓存)中强制执行适当的同步。这里说的是指

JexlEngine jexl = new Engine();

对象可在不同线程之间进行共享,具体各自执行的表达式之间不会有线程安全问题

那么到这里,关于JexlEngine的使用用我们已经有了一个基本的了解。下一步,我们将把JexlEngine结合之前的文章《基于AOP的Redis缓存注解功能开发设计》的内容,重新设计调整下,来解决该文章中提到的hashKey生成规则的问题

相关官方文档:

https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/package-summary.html#usage