首页 > 技术文章 > Jexl表达式引擎-根据字符串动态执行JAVA.md

theory 2020-07-20 10:47 原文

Table of Contents generated with DocToc

一、使用场景

在做某些项目的时候,有时会遇到如下情景:

用户需要传入某个JAVA 表达式,然后后台将这个表达式当作JAVA代码执行


二、市面上表达式引擎比较

我们有许多表达式引擎可供选择:

  • Jexl
  • Aviator

2.1 Aviator

avitor具体使用技巧可以参考这篇博客:https://blog.csdn.net/keda8997110/article/details/50782848

avitor可以满足基本的表达式的判断,但对于对象中的函数调用明显力不从心,如果碰到以下业务情景:

用户传入的java表达式是:user.getName().equals("123456");

这个表达式是调用user对象里的getName方法,然后再掉用equals方法,判断是否等于字符串123456,avitor对于调用对象的方法这个问题也有自己的解决方法:

User user = new User("123456");

//首先要注入User的类
AviatorEvaluator.addInstanceFunctions("user", User.class);

//再使用aviator特定的表达式运行方法: namespace.method(instance, args)
AviatorEvaluator.execute("User.getName(user)=='123456'")

这样太麻烦了,虽然程序可以事先将需要的类注入到AviatorEvaluator引擎中,但是我们还需要将熟悉的java表达式转化为Aviator需要的表达式形式。

那我们有没有可以直接执行java代码的呢?有!

2.2 Jexl

我们在maven中使用 Jexl 需要导入如下依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-jexl -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-jexl</artifactId>
  <version>2.1.1</version>
</dependency>

然后看我们的 Jexl 如何实现业务:

用户传入的java表达式是:user.getName().equals("123456");
然后后台直接执行

  1. 首先我们实现一个方法,用于动态加载函数
//动态加载方法
//jexlExp -- java表达式
//map -- 运行环境
private Object invokeMethod(String jexlExp, Map<String,Object> map){
    JexlEngine jexl=new JexlEngine();
    Expression e = jexl.createExpression(jexlExp);
    JexlContext jc = new MapContext();
    for(String key:map.keySet()){
        jc.set(key, map.get(key));
    }
    if(null==e.evaluate(jc)){
        return "";
    }
    return e.evaluate(jc);
}
  1. 然后编写测试用例:
pubic class User{
    String name = "123456";
    public User(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}
public class JexlTest {

    private Map<String, Object> env;

    //注入测试环境,将变量形成一个map<对象名,对象>
    @Before
    public void before(){
        env = new HashMap<>();
        env.put("id","123456");
        env.put("user",new User());
    }

    //动态加载方法
    private Object invokeMethod(String jexlExp, Map<String,Object> map){
        JexlEngine jexl=new JexlEngine();
        Expression e = jexl.createExpression(jexlExp);
        JexlContext jc = new MapContext();
        for(String key:map.keySet()){
            jc.set(key, map.get(key));
        }
        if(null==e.evaluate(jc)){
            return "";
        }
        return e.evaluate(jc);
    }


    //调用Jexl执行引擎
    @Test
    public void TestFunctionMethod(){
        String expression="user.getId().equals(id)";
        boolean res = (boolean) invokeMethod(expression,env);
        Assert.assertTrue(res);
    }
}

执行结果为true

与Aviator相比,我们省去了修改表达式的步骤

推荐阅读