spring-expression表达式

前言

Spring Expression Language(简称 SpEL)是一个支持查询和操作运行时对象的强大的表达式语言。贯穿着整个 Spring 产品组的语言。

SpEL基本语法

SpEL 字面量:

  • 整数:#{8}
  • 小数:#{8.8}
  • 科学计数法:#{1e4}
  • String:可以使用单引号或者双引号作为字符串的定界符号。
  • Boolean:#{true}
  • 对象:#{null}
//字符串
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
String str2 = parser.parseExpression("\"Hello World!\"").getValue(String.class);

//int数值类型
int int1 = parser.parseExpression("1").getValue(Integer.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
//long数值类型
long long1 = parser.parseExpression("-1L").getValue(long.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
//float数字类型
float float1 = parser.parseExpression("1.1").getValue(Float.class);
//double数字类型
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
double double2 = parser.parseExpression("1.1E2").getValue(double.class);

//布尔类型
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);

//null类型
Object null1 = parser.parseExpression("null").getValue(Object.class);

SpEL集合:

  • List,数组,字典
  • 集合,字典元素访问:使用“集合[索引]”访问集合元素,使用“map[key]”访问字典元素
  • 列表,字典,数组元素修改
  • 集合操作,指根据集合中的元素中通过选择来构造另一个集合(类似stream的操作)
//使用{表达式,……}定义内联List
parser.parseExpression("{1,2,3}").getValue(List.class);

//定义数组及初始化
parser.parseExpression("new int[1]").getValue(int[].class); 
parser.parseExpression("new int[2]{1,2}").getValue(int[].class); 

//使用{表达式,……}定义内联Map
parser.parseExpression("{'a':1,'b':2}").getValue(Map.class);

//字典的访问
parser.parseExpression("#map['a']").getValue(context3, int.class)
//List访问
parser.parseExpression("#collection[1]").getValue(context2, int.class)
parser.parseExpression("{1,2,3}[0]").getValue(int.class)

//修改
parser.parseExpression("#array[1] = 3").getValue(context1, int.class); 
parser.parseExpression("#collection[1] = 3").getValue(context2, int.class); 
parser.parseExpression("#map['a'] = 2").getValue(context3, int.class); 

//集合投影:![], 相当于stream map操作
//下面表示把集合中所有的数+1
parser.parseExpression("#collection.![#this+1]").getValue(context1, Collection.class); 
//其中投影表达式中“#this”代表每个集合或数组元素,可以使用比如“#this.property”来获取集合元素的属性,其中“#this”可以省略。
parser.parseExpression("#map.![value+1]").getValue(context2, List.class);  

//集合选择:?[],相当于stream filter操作
//下面表示把集合过滤出>4的元素
parser.parseExpression("#collection.?[#this>4]").getValue(context1, Collection.class);
//过滤出key!=a变将所有值+1
parser.parseExpression("#map.?[key != 'a'].![value+1]").getValue(context2, List.class);  



SpEL支持的运算符号:

  • 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
  • 比较运算符:< , > , == , >= , <= , !=, lt , gt , eg , le , ne
  • 逻辑运算符:and , or , not , &&, || ,!
  • if-else 运算符: 三目运算及Elivis运算表达式
  • 正则表达式:#{admin.email matches ‘[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}’}
  • 括号优先级表达式: 使用“(表达式)”构造,括号里的具有高优先级
//加减乘除
int result1 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);
//求余
int result2 = parser.parseExpression("4%3").getValue(Integer.class);
//幂运算
int result3 = parser.parseExpression("2^3").getValue(Integer.class);
//+还可以用作字符串连接
int result4 = parser.parseExpression("'hello'+ 'word'").getValue(String.class);

//比较运算符
boolean result1 = parser.parseExpression("2>1").getValue(boolean.class);  

//逻辑运算符    
boolean result2 = parser.parseExpression("!true and (NOT true or NOT false)").getValue(boolean.class); 

//三目运算符 “表达式1?表达式2:表达式3”
boolean result3 = parser.parseExpression("2>1?true:false").getValue(boolean.class));
//Elivis运算符“表达式1?:表达式2”  
//当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2
boolean result3 =parser.parseExpression("null?:false").getValue(boolean.class));

SpEL变量与对象 :

  • 访问类:使用“T(Type)”来表示java.lang.Class实例,“Type”必须是类全限定名,“java.lang”包除外
  • 调用静态方法静态属性:#{T(java.lang.Math).PI}
  • 实例化:使用java关键字“new”,类名必须是全限定名,但java.lang包内的类型除外
  • instanceof表达式:同JAVA支持instanceof运算符
  • 变量对象定义及引用:#{car}
  • 引用其他对象的属性:#{car.brand}
  • 变量对象的赋值:#{car=‘aaaaa’}
  • 对象函数的调用 , 及链式操作:#{car.toString()}
//访问类型,在java.lang下不用全限定名
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class); 
//访问类型,不在java.lang下要全限定名
Class<String> result2 = parser.parseExpression("T(cn.javass.spring.chapter5.SpELTest)").getValue(Class.class);

//类静态字段访问  
parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);  
//类静态方法调用  
parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);  

//实例化String对象,在java.lang下不用全限定名
parser.parseExpression("new String('haha')").getValue(String.class);  
//实例化Date对象,不在java.lang下要用全限定名
parser.parseExpression("new java.util.Date()").getValue(Date.class);  

// instanceof表达式
parser.parseExpression("'haha' instanceof T(String)").getValue(boolean.class));

//变量对象定义
EvaluationContext context = new StandardEvaluationContext();  
context.setVariable("variable", "haha"); 

//定义root对象变量
EvaluationContext  context = new StandardEvaluationContext("haha");  


//变量引用
parser.parseExpression("#variable").getValue(context, String.class);
  
//root变量引用
parser.parseExpression("#root").getValue(context, String.class); 

//当前运行对象引用,和java this相似
//当前表达式只在root对象中运行 此时 #this=#root
parser.parseExpression("#this").getValue(context, String.class)

//@”符号来引用spring Bean, 需要使用BeanResolver查找bean
parser.parseExpression("@systemProperties").getValue(context, Properties.class)
 

//对象属性的安全访问,防止car为null.
//用来避免“?.”前边的表达式为null时抛出空指针异常,而是返回null
parser.parseExpression("#car?.year").getValue(context, Object.class); 

//给变量赋值
parser.parseExpression("#car='aaaaa'").getValue(context, String.class)

//自定义函数的2种注册方式
Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);  
context.registerFunction("parseInt", parseInt);  
context.setVariable("parseInt2", parseInt);  
parser.parseExpression("#parseInt('3') == #parseInt2('3')").getValue(boolean.class));

//会调用 root对象下的getYear方法
parser.parseExpression("getYear()").getValue(context, int.class);  

//会调用 car对象下的getYear方法
parser.parseExpression("#car.getYear()").getValue(context, int.class);  

SpEL 原理

SpEL其实就是简易的脚本语言,其过程如下

  1. 表达式:表达式是表达式语言的核心,所以表达式语言都是围绕表达式进行的,从我们角度来看是“干什么”。即词法分析 + 语法分析
  2. 解析器:用于将字符串表达式解析为表达式对象,从我们角度来看是“谁来干”;即语义分析
  3. 上下文:表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等,从我们角度看是“在哪干”;即运行环境,相当于《语义分析(三)》中例子部分的memory对象。联系各表达多的运行状态。

接口分析

首先来看下简单的例子

public class SpelTest {

    public char isEven(int num) {
        return num % 2 == 0 ? 'Y' : 'N';
    }

    public static void main(String[] args) {
        //解析器配置
        SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, SpelTest.class.getClassLoader());

        //表达式,相当于代码
        String expressionStr = "{1,2,3,4,5,6,7,8,9,10}.![#root.isEven(#this)]";

        //通过配置,创建解析器
        ExpressionParser expressionParser = new SpelExpressionParser(configuration);

        //解析器,解析表达式(词法-语法)形成语法树
        Expression expression = expressionParser.parseExpression(expressionStr);

        //把spelTest做为当前运行环境,第一个运行环境,即root。
        //根据语法树+运行环境,进行语义分析
        SpelTest spelTest = new SpelTest();
        Object value = expression.getValue(spelTest);
        System.out.println(value);
    }
}
SpelParserConfiguration

主要为ExpressionParser提供配置,使其parseExpression或getValue的产生不同的影响。

//编译模式:解释,编译,混合(编译失败,则用解释)。
//如果选用编译的话,则会把第一次解释后的运行顺序记录使用asm技术写入到Class文件中。
//@see SpelExpression.checkCompile
private final SpelCompilerMode compilerMode;

//编译模板下要使用到的ClassLoader
private final ClassLoader compilerClassLoader;

//如果出现#root.aa.bb的表达式,aa为对象为null,则自动创建对象
//如果出现#root.aa[0]的表达式,aa为List为null,则自动创建ArrayList
//如果出现#root.aa['bb']的表达式,aa为Map为null,则自动创建HashMap
private final boolean autoGrowNullReferences;

//如果出现aa[100]的表达式,aa为容量为1的list对象,则自动扩建容量
private final boolean autoGrowCollections;

//当autoGrowCollections超过这值,则不能继续增长
private final int maximumAutoGrowSize;
ExpressionParser

表达式解析器,根据表达式构建语法树。基实现如下

//根据表达式生成语法树
//@see
Expression parseExpression(String expressionString) throws ParseException;

//提供简单的表达式模板
//程序中表达式不可能单独的出现,往往出来在字符中,区分表达式和字符基本上通过特点标识${...}
//通过ParserContext提供的解析规则很容易提取表达式
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
Expression

语法树,结合运行环境,可真实计算出值。其接口方法很多,将转化为以下代码。

//运行初始状态
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
//将初始状态跟着语法树走,从最终状态中获取结果				
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
//将结果转化为想要的类型
ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType)
ExpressionState

运行状态,可理解为运行栈

//运行上下文,相当于JVM
private final EvaluationContext relatedContext;

//运行最上层对象,main函数所在的class
//#root, 其默认值为relatedContext.rootObject
private final TypedValue rootObject;

//相当于配置文件
private final SpelParserConfiguration configuration;

//描述的应该是栈桢的功能
private Deque<TypedValue> contextObjects;

//相当于临时变量变
private Deque<VariableScope> variableScopes;
//相当于方法中的this
//#this
private ArrayDeque<TypedValue> scopeRootObjects;
StandardEvaluationContext

运行环境


//默认返回值,会被结果改变
private TypedValue rootObject;

//权限,防止EL表达式权限太大,设置有些类不能访问
private volatile List<PropertyAccessor> propertyAccessors;

//提供构造表达式的,解决方式
private volatile List<ConstructorResolver> constructorResolvers;

//提供方法调用的解析
private volatile List<MethodResolver> methodResolvers;

//支柱静态方法调用
private volatile ReflectiveMethodResolver reflectiveMethodResolver;

//支持从fatoryBean中提供对象
private BeanResolver beanResolver;

//本地class解析,如 T(System)
private TypeLocator typeLocator;

//类型转换
private TypeConverter typeConverter;

//类型比较,比较运算符
private TypeComparator typeComparator = new StandardTypeComparator();

//算数运算的默认操作
private OperatorOverloader operatorOverloader = new StandardOperatorOverloader();

//本地环境变量
private final Map<String, Object> variables = new ConcurrentHashMap<>();

主要参考

第5部分:表达式语言SpEL
SpEL你感兴趣的实现原理浅析spring-expression
spring-expression官方文档

上一篇:【转】压力测试的几个道理


下一篇:记一次结算程序的性能优化过程