【IKExpression】IKExpressionV2.0简易表达式解析器

在这里插入图片描述

1 IK表达式介绍(IK Expression Introduction)

IK Expression是一个开源的(OpenSource),可扩展的(Extensible),基于java语言开发的一个超轻量级(Super lightweight)的公式化语言解析执行工具包。

IK Expression V2.0不依赖于任何第三方的java库。它做为一个简单的jar,可以集成于任意的Java应用中。这包括了JavaEE应用(基于应用服务器的), Java桌面应用以及Java WebStart方式的应用。

IK Expression最初诞生的原因是为了能增强工作流引擎,如jBPM等对流程配置的灵活度。使其能在流程运行期获得同配置期一样灵活地对执行逻辑条件进行变更。经过扩展后的IK Expression还可以适用于各种常规业务系统的动态条件配置,如需要图形化配置应用的场合,或是模拟Excel电子表格的公式运算的场景。

同EL和BeanScript不同,IK Expression的设计目标是面向最终用户的,因此它被设计成语法简单(像数学算式),通俗易懂(支持中文变量及函数名)但功能有限的解析引擎。如果你需要一个功能强大的表达式引擎,也许IK Expression并不是最好的选择

1.1 概要(OverView)

IK Expression是一个采用逆波兰式算法结合指针栈优化的公式解析引擎,它由表达式编译、、表达式执行、变量容器、以及函数配置管理四部分构成。它具有以下特点:

  1. 支持【基础运算符】+ - × / % 【逻辑运算符】! && || 【三元运算符】?:以及特有的#集合运算。支持括号优先级,对&&,||,?:有短路优化处理。
  2. 支持函数执行,函数扩展,支持变量定义。
  3. 完整Jar包大小90K,API简单易学,超轻量级,无第三方类库依赖。

3.IK Expression 组件结构图

在这里插入图片描述

1.2 升级变更(Changes Log)

Version2.0.2的变更:

修订了自定义函数返回类型为void时,表达式校验会抛出异常的BUG。对于void返回类型,IK-Expression将当作null类型进行计算。

Version2.0.1的变更:

添加了对表达式的折行书写的支持(即对表达式中出现CRLF的支持),方便于用户编写逻辑复杂的长表达式,增强表达式可阅读性。

Version2.0.0相对于V1.0的变更

  1. 增加了“?:“三元操作符。
  2. 增强了“+“操作符,支持对null型,Date型,Boolean型的连接操作。
  3. 增加了自定义函数扩展,支持配置方式扩展函数和API编码方式扩展函数。
  4. 增加了函数别名映射功能,可配置中文函数别名。
  5. 增加了函数参数和返回值对java Object类型的支持。
  6. 优化“||”、“&&”、“?:“操作符,实现相应逻辑的短路处理。
  7. 修订了Date类型的==比较逻辑。原有逻辑判定时间差小于1秒为相等,现在更正为精确到秒。
  8. 修订了==比较逻辑对数值类型运算的bug,使其支持Integer,Float,Long,Double类型的混合比较,以及当这四种类型混合比较时,其中一个参数为null的情况。
  9. 修改了V1.0中类Variable的方法createVariable的参数。
  10. 废除了V1.0中的“:“操作符(使用“?:“三元操作符代替)
  11. 废除了V1.0中类ExpressionEvaluator的compileExpression方法。

2. 快速入门(Quick Start)

2.1 下载(Downloadables Overview)

GoogleCode开源项目:http://code.google.com/p/ik-expression/
GoogleCode SVN下载:添加链接描述

2.2 安装部署

IK Expression 的安装部署十分简单,安装包包含:

  1. 使用说明文档(即本文档)
  2. IKExpression2.0.jar
  3. functionConfig.xml

其中,IKExpression2.0.jar部署于项目的lib目录中;functionConfig.xml(函数定义配置)文件放置在代码根目录(对于web项目,通常是WEB-INF/classes目录,同hibernate、log4j等配置文件相同)下即可。

2.3 API简易教程(API Tutorial)

代码样例HelloWorld

/** 
 * Hello World Example 
 * @param args 
 */  
public static void main(String[] args){  
    if(args.length == 0){  
        args = new String[1];  
        args[0] = "IK Expression";  
    }  
    //定义表达式  
    String expression = "\"Hello World \" + 用户名";  
    //给表达式中的变量 "用户名" 付上下文的值  
    List<Variable> variables = new ArrayList<Variable>();  
    variables.add(Variable.createVariable("用户名", args[0]));  
    //执行表达式  
    Object result = ExpressionEvaluator.evaluate(expression, variables);  
    System.out.println("Result = " + result);         
}   

执行结果: Hello World IK Expression

API说明

org.wltea.expression.ExpressionEvaluator
方法1:

public static Object evaluate(String expression, Collection<Variable> variables)

说明:传入表达式和表达式上下文的变量,执行表达式返回结果

参数1 :String expression, 要传入执行的表达式
参数2 :Collection<Variable> variables 表达式上下文的变量集合(详细看类org.wltea.expression.datameta.Variable的说明)。
返回值:表达式执行结果,可能是以下类型的java对象中的一种:Int、Long、Float、Double、Boolean、String、Date、List、Object。

方法2:

public static Object evaluate(String expression)

说明:对方法1的重载,执行简单的没有变量的表达式。请参考方法1说明.

类org.wltea.expression.datameta.Variable
       
该类是用来表示表达式的上下文变量的,上面的例子中用到了别名为“用户名”的上下
文变量,这是也是表达式最有用的地方。例如,在jBPM的流程定义中,我们需要定义
一个报销审批流程中,用来决定流程分支走向的表达式:   
(申请金额 > 10000)?“总经理审批”:“部门经理审批”  

这里需要定义一个别名为“申请金额“变量。变量通过evaluate(String expression, Collection<Variable> variables)方法中的variables参数传入表达式中。而Variable类型变量的构造十分的简单,它是标准的POJO。

方法1:

public static Variable createVariable(String varName , Object varValue)

说明:根据参数别名和参数值,构造 Variable 实例

  1. 参数1 :String varName, 参数的别名,可以是中文别名
  2. 参数2 :Object varValue,参数的值 , 可以是下类型的java对象中的一种:
    Int、Long、Float、Double、Boolean、String、Date、List、Object。
  3. 返回值:org.wltea.expression.datameta.Variable类的实例

方法2:(直接使用构造函数)

public Variable(String varName , DataType varDataType , Object varValue)

说明:根据指定的参数类型、参数别名和参数值,构造 Variable 实例

  1. 参数1 :String varName, 参数的别名,可以是中文别名
  2. 参数2 :DataType varDataType, 变量类型,它是
    org.wltea.expression.datameta. BaseDataMeta.DataType枚举类型,包括的枚举值有:
//NULL类型 DATATYPE_NULL ,
//字符窜 DATATYPE_STRING ,
//布尔类 DATATYPE_BOOLEAN ,
//整型数 DATATYPE_INT ,
//长整型数 DATATYPE_LONG ,
//浮点数 DATATYPE_FLOAT ,
//双精度浮点 DATATYPE_DOUBLE ,
//日期时间 DATATYPE_DATE ,
//集合对象 DATATYPE_LIST,
//通用对象类型         DATATYPE_OBJECT,
  1. 参数3 :Object varValue,参数的值 , 可以是下类型的java对象中的一种:
    Int、Long、Float、Double、Boolean、String、Date、List、Object。
  2. 返回值:org.wltea.expression.datameta.Variable类的实例

3. 表达式公式规范(Expression Formula Specification)

3.1 数据类型(Types, Values, and Variables)

数字型
i. 整形 integer : -2321 , 34234
ii. 长整型 long :3245235235L
iii. 单精度浮点 float : 342.555F
iv. 双精度浮点 double: 234234.3423

字符型:“a-zA-Z012456789”
布尔型:true、false
日期时间型:[2008-08-08] 或 [2009-01-01 12:33:14]
扩展类型:List对象集合 (该类型不支持表达式字面定义,由操作符或函数运算结果生成)
通用对象:Object类型 (该类型不支持表达式字面定义,由操作符或函数运算结果生成)

3.2 运算符(Operators)

在这里插入图片描述

在这里插入图片描述

3.3 分割符(Separators)

a) 括号 "("  ")" —— 标识优先级
b) 逗号   "," —— 分隔函数的参数
c) 方括号 "["  "]" —— 标识日期型常量
d) 双引号 """ —— 标识字符型常量
e) 美元号   "$" —— 函数标识前缀
f) 转义符   "\" —— 字符串转义,支持\\ , \”, \r , \n , \t

3.4 内部函数(Inner Functions)

内置函数是目前解析器已经实现的一些非常简单、实用的函数
在这里插入图片描述

3.5 语法约束(Lexical Structure)

  1. 变量命名遵循java变量命名规范(如,不能以数字打头,不能用系统操作符打头等)。
  2. 函数声明以“$”符号打头,自定义函数命名遵循java方法命名规范。
  3. 日期型常量使用“[]”符号界定,格式为 [yyyy-MM-dd 24h-mm-ss],不支持毫秒。
  4. 用户自定义函数别名不能重复。(详细请参阅本文4.1章节)
  5. 用户自定义函数的参数和返回值类型限定于3.1章节描述的数据类型。(详细请参阅本文4.1章节)

3.6 公式样例(Formula Example)

【1】 +、- 、* 、/ 、%(取模) 常规的算术运算,支持括号优先级 :
如:常见的OA中用于年休假工资计算公式

3000 / 21.5 *12 - 转正月份)/ 2 

其中,“转正月份”可以是上下文变量

【2】不同数据类型的字符串连接 :

“ABC”+123+10) 运算结果 “ABC133”
“ABC”+ 123 + 10 运算结果“ABC12310”
“[2009-08-08] + false + 123 + \"a String\" + null”

运算结果 “2009-08-08 00:00:00false123a String” (PS:忽略 null型变量)

【3】 > >= < <= == !=逻辑比较运算,返回布尔值 :

3-1.数值大小比较 : 1234>223 运算结果 true
3-2.字符大小比较 :1234>223” 运算结果 fasle
3-3.日期大小比较 : [2008-12-23] >= [2008-08-08] —— true
3-4.同null的 ==!= 比较 : 申请人!=null  (其中,“申请人”为执行上下文的变量)

【4】逻辑与、逻辑或、逻辑非运算:

true  &&  $DAYEQUALS([2008-01-01] , [2008-11-01]) ——false
true  ||  $DAYEQUALS([2008-01-01] , [2008-11-01]) —— true
true  && !$DAYEQUALS([2008-01-01] , [2008-11-01]) —— true

【5】结果连接运算“#”:

1000/10  #  [2008-12-23]>$SYSDATE()  # “ABC”+123
运算结果 包含 100false , “ABC123”三种不同类型对象的List

【6】函数与操作符混合、嵌套调用,如:

$DAYEQUALS(  
    $CALCDATE(  
        $SYSDATE() , 0 , 0 ,   
        (8+11-5*(6/3)) * (2- 59 % 7) ,  
        0 ,0,0 ),  
    [2009-10-01]  
)    

运算结果 false

4. 高级特性(Advance)

4.1 函数定制(Functions Customize)

IK-Expression最吸引人的特性莫过于它允许你以非常简单的方式扩展你的自定义函数。IK-Expression带有一个xml配置文件functionConfig.xml。在使用IK-Expression时,该配置文件应放置于class的根目录中(如同spring和hibernate等的配置文件一样)。配置文件内部格式如下:functionConfig.xml
Xml代码 收藏代码

<?xml version="1.0" encoding="UTF-8"?>  
<function-configuration>  
    <!-- 系统函数默认配置 -->  
    <bean class="org.wltea.expression.function.SystemFunctions">  
        <function name="CONTAINS" method="contains">  
            <parameter-type>java.lang.String</parameter-type>  
            <parameter-type>java.lang.String</parameter-type>  
        </function>  
        <function name="STARTSWITH" method="startsWith">  
            <parameter-type>java.lang.String</parameter-type>  
            <parameter-type>java.lang.String</parameter-type>  
        </function>  
        <function name="ENDSWITH" method="endsWith">  
            <parameter-type>java.lang.String</parameter-type>  
            <parameter-type>java.lang.String</parameter-type>  
        </function>  
        <function name="CALCDATE" method="calcDate">  
            <parameter-type>java.util.Date</parameter-type>  
            <parameter-type>int</parameter-type>  
            <parameter-type>int</parameter-type>  
            <parameter-type>int</parameter-type>  
            <parameter-type>int</parameter-type>  
            <parameter-type>int</parameter-type>  
            <parameter-type>int</parameter-type>  
        </function>  
        <function name="SYSDATE" method="sysDate" />  
        <function name="DAYEQUALS" method="dayEquals">  
            <parameter-type>java.util.Date</parameter-type>  
            <parameter-type>java.util.Date</parameter-type>  
        </function>  
    </bean>  
      
    <!-- 用户函数配置  ,请在这里定制您自己的函数-->  
  
</function-configuration>  

配置文件中默认配置了系统内部函数定义在没有绝对必要的原因下,不建议修改系统默认函数配置)。在默认配置的下方,用户可以定义自己的函数,格式如下:

用户自定义函数配置

<bean class="org.wltea.expression.test.TestFunctions">  
<constructor-args>  
    <constructor-arg type="java.lang.Integer">123</constructor-arg>  
    <constructor-arg type="java.lang.String">aa</constructor-arg>  
</constructor-args>  
<function name="问好" method="sayHello">  
    <parameter-type>java.lang.String</parameter-type>  
</function>  
</bean>  

这里自定义了一个名称为“问好”的函数,它有一个String类型的参数。该函数映射对应于org.wltea.expression.test.TestFunctions类的sayHello方法,而类org.wltea.expression.test.TestFunctions具有一个构造函数,构造函数带有Integer型和String型的参数。配置中给出了构造函数的初始化参数“123”和“aa”

通过上述定义,用户就可以在表达式中使用该函数,如:

   $问好(当前用户)

其中“当前用户”为表达式的上下文变量。

上述例子直观的展示了用户函数自定义的过程。下面,我们将系统的了解一下IK-Expression的函数扩展定义规则和约束:

  1. IK-Expression中,函数直接对应于java的一个类的一个明确的方法。如:$CONTAINS对应org.wltea.expression.function.SystemFunctions类的contains方法;

  2. 所有的函数定义前,必须先定义对应的java类。如果该类使用带参数的构造函数,则必须提供明确的构造参数,如:

<bean class="org.wltea.expression.test.TestFunctions">  
    <constructor-args>  
        <constructor-arg type="java.lang.Integer">123</constructor-arg>  
        <constructor-arg type="java.lang.String">aa</constructor-arg>  
    </constructor-args>  
    <function name="问好" method="sayHello">  
     <parameter-type>java.lang.String</parameter-type>  
    </function>  
</bean>  
  1. java类的加载和实例化在初始化阶段一次性完成。目前IK-Expression仅支持单例形式的加载,即对一个java类仅实例化一次。

  2. 定义函数时,必须明确定义函数对应java方法,以及java方法的参数类型和顺序,如:

<function name="CONTAINS" method="contains">  
    <parameter-type>java.lang.String</parameter-type>  
    <parameter-type>java.lang.String</parameter-type>  
</function>  

你可以使用不同的函数名(英文的和中文的),对应相同的java方法,但对java中的方法重载必须使用不同的函数名对应(即,IK-Expression不支持函数名重载)

  1. 函数的参数和返回值只能是IK-Expression支持的数据类型(请参考3.1数据类型 章节).
  2. 为了增加灵活性,IK-Expression给出了一个通过编码方式添加自定义函数的static方法。

函数扩展API说明

org.wltea.expression.function.FunctionLoader

方法1:

public static void addFunction(String functionName, Object instance, Method method)

参数1 :String functionName, 要定义的函数名称(中英文皆可)。
参数2 :Object instance 函数要映射的java 类的实例。
参数3 :Method method 函数要映射的java 类的方法对象。

4.2 案例

<?xml version="1.0" encoding="UTF-8"?>
<function-configuration>
	<!-- 系统函数默认配置 -->
	<bean class="org.wltea.expression.function.SystemFunctions">
		<function name="CONTAINS" method="contains">
			<parameter-type>java.lang.String</parameter-type>
			<parameter-type>java.lang.String</parameter-type>
		</function>
		<function name="STARTSWITH" method="startsWith">
			<parameter-type>java.lang.String</parameter-type>
			<parameter-type>java.lang.String</parameter-type>
		</function>
		<function name="ENDSWITH" method="endsWith">
			<parameter-type>java.lang.String</parameter-type>
			<parameter-type>java.lang.String</parameter-type>
		</function>
		<function name="CALCDATE" method="calcDate">
			<parameter-type>java.util.Date</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
		</function>
		<function name="SYSDATE" method="sysDate" />
		<function name="DAYEQUALS" method="dayEquals">
			<parameter-type>java.util.Date</parameter-type>
			<parameter-type>java.util.Date</parameter-type>
		</function>
	</bean>
	
	<!-- 用户函数配置  请在这里定制您自己的函数-->
	<bean class="fun.ExpressionUtil">
		<function name="timeHourRange" method="timeHourRange">
			<parameter-type>java.lang.String</parameter-type>
			<parameter-type>int</parameter-type>
			<parameter-type>int</parameter-type>
		</function>
		<function name="patternFunction" method="patternFunction">
			<parameter-type>java.lang.String</parameter-type>
		</function>
	</bean>


</function-configuration>

然后定义函数实现类

package fun;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by AH on 2016/11/18.
 */
public class ExpressionUtil {

    private static String patternString = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$";

    private static Pattern pattern = Pattern.compile(patternString);

    public static boolean timeHourRange(String time, int startHour, int endHour) {
        if (time == null || time.length() == 0) {
            return false;
        }
        int timeValue = -1;
        timeValue = Integer.parseInt(time.substring(11, 13));
        return timeValue != -1 && startHour <= endHour && timeValue >= startHour && timeValue <= endHour;
    }


    public static boolean patternFunction(String data ) {
        if (data == null ) {
            return false;
        }

        Matcher result = pattern.matcher( data);
        if(result.find()){
//            System.out.println(result.group(0));
            return true;
        }
        return false;
    }


}

最后使用

	/**
     * 测试点:测试  ikexpression 匹配 email
     * 总耗时秒:100
     * 每秒处理条数:100000
     */
    @Test
    public void ikexpressionEamilTest1() {

        String experssion = "$patternFunction(data)";

        //给表达式中的变量 [版本] 付上下文的值
        Map<String, Object> testContext = new HashMap<>();
        testContext.put("data", "[email protected]");
        boolean result = IKExpression.evaluation(experssion, testContext);
        System.out.println(result);
    }

https://www.iteye.com/blog/linliangyi2007-337069

几款不错的java表达式引擎

https://blog.csdn.net/code52/article/details/7243392

https://www.iteye.com/blog/yanguz123-2146176

https://code.google.com/archive/p/fast-el/wikis/Performance.wiki

https://www.iteye.com/news/24711-Fel-java-script

原创文章 1444 获赞 480 访问量 175万+

猜你喜欢

转载自blog.csdn.net/qq_21383435/article/details/106090296
今日推荐