ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

spring-expression表达式

2020-12-12 23:29:07  阅读:232  来源: 互联网

标签:parseExpression int spring parser getValue expression class 表达式


前言

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官方文档

标签:parseExpression,int,spring,parser,getValue,expression,class,表达式
来源: https://blog.csdn.net/y3over/article/details/111000945

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有