Spring实用技术SpEL表达式
距离上次系统认识SpEL表达式已经有7、8年了,虽说我们在做spring的项目经常会用到,但是却又常常容易被人所忽略。刚好最近两个项目使用了SpEL来做一些比较核心的功能,借此一起来回顾下吧SpEL表达式
SpEL:(springexpressionlanguage)是一种表达式语言,是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。
尽管有其他可选的Java表达式语言,如OGNL,MVEL,JBossEL等等,但Spel创建的初衷是了给Spring社区提供一种简单而高效的表达式语言,一种可贯穿整个Spring产品组的语言。这种语言的特性应基于Spring产品的需求而设计。
他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,也可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置。
虽然SpEL引擎作为Spring组合里的表达式解析的基础,但它不直接依赖于Spring,可独立使用。SpEL的作用
简化开发,减少了一些逻辑、配置的编写,使代码更加简洁。功能清单
SpEL支持以下功能文字表达式布尔和关系运算符正则表达式类表达式访问properties,arrays,lists,maps方法调用关系运算符参数调用构造函数Bean引用构造Array内嵌lists内嵌maps三元运算符变量用户定义的函数集合投影集合筛选模板表达式初识SpEL代码publicvoidtest(){ExpressionParserparsernewSpelExpressionParser();Expressionexpressionparser。parseExpression((HelloWorld)。concat(end));EvaluationContextcontextnewStandardEvaluationContext();context。setVariable(end,!);System。out。println(expression。getValue(context));}
输出HelloWorld!
分析上述代码,可以将SpEL表达式分为这几个部分:
1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
3)构造上下文:准备比如变量定义等等表达式需要的上下文数据。
4)求值:通过Expression接口的getValue方法根据上下文获得表达式值。SpEL原理及接口
SpEL提供简单的接口从而简化用户使用,在介绍原理前让我们学习下几个概念:
一、表达式:表达式是表达式语言的核心,所以表达式语言都是围绕表达式进行的,从我们角度来看是干什么;
二、解析器:用于将字符串表达式解析为表达式对象,从我们角度来看是谁来干;
三、上下文:表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等,从我们角度看是在哪干;
四、根对象及活动上下文对象:根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象,从我们角度看是对谁干。工作原理
理解了这些概念后,让我们看下SpEL如何工作的呢,如图所示:
1。首先定义表达式:12;
2。定义解析器ExpressionParser实现,SpEL提供默认实现SpelExpressionParser;
2。1。SpelExpressionParser解析器内部使用Tokenizer类进行词法分析,即把字符串流分析为记号流,记号在SpEL使用Token类来表示;
2。2。有了记号流后,解析器便可根据记号流生成内部抽象语法树;在SpEL中语法树节点由SpelNode接口实现代表:如OpPlus表示加操作节点、IntLiteral表示int型字面量节点;使用SpelNodel实现组成了抽象语法树;
2。3。对外提供Expression接口来简化表示抽象语法树,从而隐藏内部实现细节,并提供getValue简单方法用于获取表达式值;SpEL提供默认实现为SpelExpression;
3。定义表达式上下文对象(可选),SpEL使用EvaluationContext接口表示上下文对象,用于设置根对象、自定义变量、自定义函数、类型转换器等,SpEL提供默认实现StandardEvaluationContext;
4。使用表达式对象根据上下文对象(可选)求值(调用表达式对象的getValue方法)获得结果。SpEL的主要接口
ExpressionParser
表示解析器,默认实现是org。springframework。expression。spel。standard包中的SpelExpressionParser类,使用parseExpression方法将字符串表达式转换为Expression对象,对于ParserContext接口用于定义字符串表达式是不是模板,及模板开始与结束字符:publicinterfaceExpressionParser{ExpressionparseExpression(StringexpressionString)throwsParseException;ExpressionparseExpression(StringexpressionString,ParserContextcontext)throwsParseException;}
来看下示例:TestpublicvoidtestParserContext(){ExpressionParserparsernewSpelExpressionParser();ParserContextparserContextnewParserContext(){OverridepublicbooleanisTemplate(){returntrue;}OverridepublicStringgetExpressionPrefix(){return{;}OverridepublicStringgetExpressionSuffix(){return};}};Stringtemplate{Hello}{World!};Expressionexpressionparser。parseExpression(template,parserContext);System。out。println(expression。getValue());}
在此我们演示的是使用ParserContext的情况,此处定义了ParserContext实现:定义表达式是模块,表达式前缀为{,后缀为};使用parseExpression解析时传入的模板必须以{开头,以}结尾,如{Hello}{World!}。
默认传入的字符串表达式不是模板形式,如之前演示的HelloWorld。
EvaluationContext
表示上下文环境,默认实现是org。springframework。expression。spel。support包中的StandardEvaluationContext类,使用setRootObject方法来设置根对象,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等等。
Expression
表示表达式对象,默认实现是org。springframework。expression。spel。standard包中的SpelExpression,提供getValue方法用于获取表达式值,提供setValue方法用于设置对象值。
了解了SpEL原理及接口,接下来的事情就是SpEL语法了。SpEL使用与示例
基本表达式字面量表达式
SpEL支持的字面量包括:字符串、数字类型(int、long、float、double)、布尔类型、null类型。
类型
示例
字符串
Stringstr1parser。parseExpression(HelloWorld!)。getValue(String。class);
数字类型
intint1parser。parseExpression(1)。getValue(Integer。class);
longlong1parser。parseExpression(1L)。getValue(long。class);
floatfloat1parser。parseExpression(1。1)。getValue(Float。class);
doubledouble1parser。parseExpression(1。1E2)。getValue(double。class);
inthex1parser。parseExpression(0xa)。getValue(Integer。class);
longhex2parser。parseExpression(0xaL)。getValue(long。class);
布尔类型
booleantrue1parser。parseExpression(true)。getValue(boolean。class);
booleanfalse1parser。parseExpression(false)。getValue(boolean。class);
null类型
Objectnull1parser。parseExpression(null)。getValue(Object。class);Testpublicvoidtest2(){ExpressionParserparsernewSpelExpressionParser();Stringstr1parser。parseExpression(HelloWorld!)。getValue(String。class);intint1parser。parseExpression(1)。getValue(Integer。class);longlong1parser。parseExpression(1L)。getValue(long。class);floatfloat1parser。parseExpression(1。1)。getValue(Float。class);doubledouble1parser。parseExpression(1。1E2)。getValue(double。class);inthex1parser。parseExpression(0xa)。getValue(Integer。class);longhex2parser。parseExpression(0xaL)。getValue(long。class);booleantrue1parser。parseExpression(true)。getValue(boolean。class);booleanfalse1parser。parseExpression(false)。getValue(boolean。class);Objectnull1parser。parseExpression(null)。getValue(Object。class);System。out。println(str1str1);System。out。println(int1int1);System。out。println(long1long1);System。out。println(float1float1);System。out。println(double1double1);System。out。println(hex1hex1);System。out。println(hex2hex2);System。out。println(true1true1);System。out。println(false1false1);System。out。println(null1null1);}
输出str1HelloWorld!int11long11float11。1double1110。0hex110hex210true1truefalse1falsenull1null算数运算表达式
SpEL支持加()、减()、乘()、除()、求余()、幂()运算。
类型
示例
加减乘除
intresult1parser。parseExpression(12342)。getValue(Integer。class);3
求余
intresult2parser。parseExpression(43)。getValue(Integer。class);1
幂运算
intresult3parser。parseExpression(23)。getValue(Integer。class);8
SpEL还提供求余(MOD)和除(DIV)而外两个运算符,与和等价,不区分大小写。关系表达式
等于()、不等于(!)、大于()、大于等于()、小于()、小于等于(),区间(between)运算。
如parser。parseExpression(12)。getValue(boolean。class);将返回false;
而parser。parseExpression(1between{1,2})。getValue(boolean。class);将返回true。
between运算符右边操作数必须是列表类型,且只能包含2个元素。第一个元素为开始,第二个元素为结束,区间运算是包含边界值的,即xxxlist。get(0)xxxlist。get(1)。
SpEL同样提供了等价的EQ、NE、GT、GE、LT、LE来表示等于、不等于、大于、大于等于、小于、小于等于,不区分大小写。Testpublicvoidtest3(){ExpressionParserparsernewSpelExpressionParser();booleanv1parser。parseExpression(12)。getValue(boolean。class);booleanbetween1parser。parseExpression(1between{1,2})。getValue(boolean。class);System。out。println(v1v1);System。out。println(between1between1);}
输出v1falsebetween1true逻辑表达式
且(and或者)、或(or或者)、非(!或NOT)。Testpublicvoidtest4(){ExpressionParserparsernewSpelExpressionParser();booleanresult1parser。parseExpression(21and(!trueor!false))。getValue(boolean。class);booleanresult2parser。parseExpression(21(!true!false))。getValue(boolean。class);booleanresult3parser。parseExpression(21and(NOTtrueorNOTfalse))。getValue(boolean。class);booleanresult4parser。parseExpression(21(NOTtrueNOTfalse))。getValue(boolean。class);System。out。println(result1result1);System。out。println(result2result2);System。out。println(result3result3);System。out。println(result4result4);}
输出result1trueresult2trueresult3trueresult4false字符串连接及截取表达式
使用进行字符串连接,使用String〔0〕〔index〕来截取一个字符,目前只支持截取一个,如HelloWorld!得到HelloWorld!;而HelloWorld!〔0〕将返回H。三目运算
三目运算符表达式1?表达式2:表达式3用于构造三目运算表达式,如21?true:false将返回true;Elivis运算符
Elivis运算符表达式1?:表达式2从Groovy语言引入用于简化三目运算符的,当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2,简化了三目运算符方式表达式1?表达式1:表达式2,如null?:false将返回false,而true?:false将返回true;正则表达式
使用strmatchesregex,如123matchesd{3}将返回true;括号优先级表达式
使用(表达式)构造,括号里的具有高优先级。
类相关表达式类类型表达式
使用T(Type)来表示java。lang。Class实例,Type必须是类全限定名,java。lang包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
具体使用方法如下:TestpublicvoidtestClassTypeExpression(){ExpressionParserparsernewSpelExpressionParser();java。lang包类访问ClassStringresult1parser。parseExpression(T(String))。getValue(Class。class);System。out。println(result1);其他包类访问Stringexpression2T(com。demo。spel。SpelTest);ClassSpelTestvalueparser。parseExpression(expression2)。getValue(Class。class);System。out。println(valueSpelTest。class);类静态字段访问intresult3parser。parseExpression(T(Integer)。MAXVALUE)。getValue(int。class);System。out。println(result3Integer。MAXVALUE);类静态方法调用intresult4parser。parseExpression(T(Integer)。parseInt(1))。getValue(int。class);System。out。println(result4);}
输出classjava。lang。Stringtruetrue1
对于java。lang包里的可以直接使用T(String)访问;其他包必须是类全限定名;可以进行静态字段访问如T(Integer)。MAXVALUE;也可以进行静态方法访问如T(Integer)。parseInt(1)。类实例化
类实例化同样使用java关键字new,类名必须是全限定名,但java。lang包内的类型除外,如String、Integer。TestpublicvoidtestConstructorExpression(){ExpressionParserparsernewSpelExpressionParser();Stringresult1parser。parseExpression(newString(我是小菠菜))。getValue(String。class);System。out。println(result1);Dateresult2parser。parseExpression(newjava。util。Date())。getValue(Date。class);System。out。println(result2);}
实例化完全跟Java内方式一样,运行输出我是小菠菜WedAug0320:22:43CST2022instanceof表达式
SpEL支持instanceof运算符,跟Java内使用同义;如hahainstanceofT(String)将返回true。TestpublicvoidtestInstanceOfExpression(){ExpressionParserparsernewSpelExpressionParser();Booleanvalueparser。parseExpression(我爱JAVAinstanceofT(String))。getValue(Boolean。class);System。out。println(value);}
输出true变量定义及引用
变量定义通过EvaluationContext接口的setVariable(variableName,value)方法定义;在表达式中使用variableName引用;除了引用自定义变量,SpE还允许引用根对象及当前上下文对象,使用root引用根对象,使用this引用当前上下文对象;TestpublicvoidtestVariableExpression(){ExpressionParserparsernewSpelExpressionParser();EvaluationContextcontextnewStandardEvaluationContext();context。setVariable(name,我是小菠菜);context。setVariable(lesson,Spring系列);获取name变量,lesson变量Stringnameparser。parseExpression(name)。getValue(context,String。class);System。out。println(name);Stringlessonparser。parseExpression(lesson)。getValue(context,String。class);System。out。println(lesson);StandardEvaluationContext构造器传入root对象,可以通过root来访问root对象contextnewStandardEvaluationContext(我是root对象);StringrootObjparser。parseExpression(root)。getValue(context,String。class);System。out。println(rootObj);this用来访问当前上线文中的对象StringthisObjparser。parseExpression(this)。getValue(context,String。class);System。out。println(thisObj);}
输出我是小菠菜Spring系列我是root对象我是root对象
使用variable来引用在EvaluationContext定义的变量;除了可以引用自定义变量,还可以使用root引用根对象,this引用当前上下文对象,此处this即根对象。自定义函数
目前只支持类静态方法注册为自定义函数;SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数,其实完全可以使用setVariable代替,两者其实本质是一样的;TestpublicvoidtestFunctionExpression()throwsSecurityException,NoSuchMethodException{定义2个函数,registerFunction和setVariable都可以,不过从语义上面来看用registerFunction更恰当StandardEvaluationContextcontextnewStandardEvaluationContext();MethodparseIntInteger。class。getDeclaredMethod(parseInt,String。class);context。registerFunction(parseInt1,parseInt);context。setVariable(parseInt2,parseInt);ExpressionParserparsernewSpelExpressionParser();System。out。println(parser。parseExpression(parseInt1(3))。getValue(context,int。class));System。out。println(parser。parseExpression(parseInt2(3))。getValue(context,int。class));Stringexpression1parseInt1(3)parseInt2(3);booleanresult1parser。parseExpression(expression1)。getValue(context,boolean。class);System。out。println(result1);}
此处可以看出registerFunction和setVariable都可以注册自定义函数,但是两个方法的含义不一样,推荐使用registerFunction方法注册自定义函数。
运行输出33true表达式赋值
使用ExpressionsetValue方法可以给表达式赋值TestpublicvoidtestAssignExpression1(){ObjectusernewObject(){privateStringname;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this。namename;}OverridepublicStringtoString(){returnclassname{namename};}};{user为root对象ExpressionParserparsernewSpelExpressionParser();EvaluationContextcontextnewStandardEvaluationContext(user);parser。parseExpression(root。name)。setValue(context,我是小菠菜);System。out。println(parser。parseExpression(root)。getValue(context,user。getClass()));}{user为变量ExpressionParserparsernewSpelExpressionParser();EvaluationContextcontextnewStandardEvaluationContext();context。setVariable(user,user);parser。parseExpression(user。name)。setValue(context,我是小菠菜);System。out。println(parser。parseExpression(user)。getValue(context,user。getClass()));}}
运行输出classname{name我是小菠菜}classname{name我是小菠菜}对象属性存取及安全导航表达式
对象属性获取非常简单,即使用如a。property。property这种点缀式获取,SpEL对于属性名首字母是不区分大小写的;SpEL还引入了Groovy语言中的安全导航运算符(对象属性)?。属性,用来避免?。前边的表达式为null时抛出空指针异常,而是返回null;修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改。publicstaticclassCar{privateStringname;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this。namename;}OverridepublicStringtoString(){returnCar{namename};}}publicstaticclassUser{privateCarcar;publicCargetCar(){returncar;}publicvoidsetCar(Carcar){this。carcar;}OverridepublicStringtoString(){returnUser{carcar};}}Testpublicvoidtest5(){UserusernewUser();EvaluationContextcontextnewStandardEvaluationContext();context。setVariable(user,user);ExpressionParserparsernewSpelExpressionParser();使用。符号,访问user。car。name会报错,原因:user。car为空try{System。out。println(parser。parseExpression(user。car。name)。getValue(context,String。class));}catch(EvaluationExceptionParseExceptione){System。out。println(出错了:e。getMessage());}使用安全访问符号?。,可以规避null错误System。out。println(parser。parseExpression(user?。car?。name)。getValue(context,String。class));CarcarnewCar();car。setName(保时捷);user。setCar(car);System。out。println(parser。parseExpression(user?。car?。toString())。getValue(context,String。class));}
运行输出出错了:EL1007E:PropertyorfieldnamecannotbefoundonnullnullCar{name保时捷}
对象方法调用
对象方法调用更简单,跟Java语法一样;如haha。substring(2,4)将返回ha;而对于根对象可以直接调用方法;
Bean引用
SpEL支持使用符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean,Spring提供BeanFactoryResolver实现。Testpublicvoidtest6(){DefaultListableBeanFactoryfactorynewDefaultListableBeanFactory();UserusernewUser();CarcarnewCar();car。setName(保时捷);user。setCar(car);factory。registerSingleton(user,user);StandardEvaluationContextcontextnewStandardEvaluationContext();context。setBeanResolver(newBeanFactoryResolver(factory));ExpressionParserparsernewSpelExpressionParser();UseruserBeanparser。parseExpression(user)。getValue(context,User。class);System。out。println(userBean);System。out。println(userBeanfactory。getBean(user));}
运行输出User{carCar{name保时捷}}true
集合相关表达式内联List
从Spring3。0。4开始支持内联List,使用{表达式,}定义内联List,如{1,2,3}将返回一个整型的ArrayList,而{}将返回空的List,对于字面量表达式列表,SpEL会使用java。util。Collections。unmodifiableList方法将列表设置为不可修改。Testpublicvoidtest7(){ExpressionParserparsernewSpelExpressionParser();将返回不可修改的空ListListIntegerresult2parser。parseExpression({})。getValue(List。class);对于字面量列表也将返回不可修改的ListListIntegerresult1parser。parseExpression({1,2,3})。getValue(List。class);Assert。assertEquals(newInteger(1),result1。get(0));try{result1。set(0,2);}catch(Exceptione){e。printStackTrace();}对于列表中只要有一个不是字面量表达式,将只返回原始List,不会进行不可修改处理Stringexpression3{{12,24},{3,44}};ListListIntegerresult3parser。parseExpression(expression3)。getValue(List。class);result3。get(0)。set(0,1);System。out。println(result3);声明二维数组并初始化int〔〕result4parser。parseExpression(newint〔2〕{1,2})。getValue(int〔〕。class);System。out。println(result4〔1〕);定义一维数组并初始化int〔〕result5parser。parseExpression(newint〔1〕)。getValue(int〔〕。class);System。out。println(result5〔0〕);}
输出java。lang。UnsupportedOperationExceptionatjava。util。CollectionsUnmodifiableList。set(Collections。java:1311)atcom。demo。spel。SpelTest。test7(SpelTest。java:315)〔〔1,6〕,〔3,8〕〕20内联数组
和Java数组定义类似,只是在定义时进行多维数组初始化。int〔〕〔〕〔〕result4parser。parseExpression(newint〔1〕〔2〕〔3〕{{1}{2}{3}})。getValue(int〔〕〔〕〔〕。class);集合,字典元素访问
SpEL目前支持所有集合类型和字典类型的元素访问,使用集合〔索引〕访问集合元素,使用map〔key〕访问字典元素;SpEL内联List访问intresult1parser。parseExpression({1,2,3}〔0〕)。getValue(int。class);SpEL目前支持所有集合类型的访问CollectionIntegercollectionnewHashSetInteger();collection。add(1);collection。add(2);EvaluationContextcontext2newStandardEvaluationContext();context2。setVariable(collection,collection);intresult2parser。parseExpression(collection〔1〕)。getValue(context2,int。class);SpEL对Map字典元素访问的支持MapString,IntegermapnewHashMapString,Integer();map。put(a,1);EvaluationContextcontext3newStandardEvaluationContext();context3。setVariable(map,map);intresult3parser。parseExpression(map〔a〕)。getValue(context3,int。class);列表,字典,数组元素修改
可以使用赋值表达式或Expression接口的setValue方法修改;Testpublicvoidtest8(){ExpressionParserparsernewSpelExpressionParser();修改list元素值ListIntegerlistnewArrayListInteger();list。add(1);list。add(2);EvaluationContextcontext1newStandardEvaluationContext();context1。setVariable(collection,list);parser。parseExpression(collection〔1〕)。setValue(context1,4);intresult1parser。parseExpression(collection〔1〕)。getValue(context1,int。class);System。out。println(result1);修改map元素值MapString,IntegermapnewHashMapString,Integer();map。put(a,1);EvaluationContextcontext2newStandardEvaluationContext();context2。setVariable(map,map);parser。parseExpression(map〔a〕)。setValue(context2,4);Integerresult2parser。parseExpression(map〔a〕)。getValue(context2,int。class);System。out。println(result2);}
输出44集合投影
在SQL中投影指从表中选择出列,而在SpEL指根据集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素;SpEL使用(listmap)。!〔投影表达式〕来进行投影运算:Testpublicvoidtest9(){ExpressionParserparsernewSpelExpressionParser();1。测试集合或数组ListIntegerlistnewArrayListInteger();list。add(4);list。add(5);EvaluationContextcontext1newStandardEvaluationContext();context1。setVariable(list,list);CollectionIntegerresult1parser。parseExpression(list。!〔this1〕)。getValue(context1,Collection。class);result1。forEach(System。out::println);System。out。println();2。测试字典MapString,IntegermapnewHashMapString,Integer();map。put(a,1);map。put(b,2);EvaluationContextcontext2newStandardEvaluationContext();context2。setVariable(map,map);ListIntegerresult2parser。parseExpression(map。!〔value1〕)。getValue(context2,List。class);result2。forEach(System。out::println);}
对于集合或数组使用如上表达式进行投影运算,其中投影表达式中this代表每个集合或数组元素,可以使用比如this。property来获取集合元素的属性,其中this可以省略。
Map投影最终只能得到List结果,如上所示,对于投影表达式中的this将是Map。Entry,所以可以使用value来获取值,使用key来获取键。集合选择
在SQL中指使用select进行选择行数据,而在SpEL指根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合,SpEL使用(listmap)。?〔选择表达式〕,其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。Testpublicvoidtest10(){ExpressionParserparsernewSpelExpressionParser();1。测试集合或数组ListIntegerlistnewArrayListInteger();list。add(1);list。add(4);list。add(5);list。add(7);EvaluationContextcontext1newStandardEvaluationContext();context1。setVariable(list,list);CollectionIntegerresult1parser。parseExpression(list。?〔this4〕)。getValue(context1,Collection。class);result1。forEach(System。out::println);System。out。println();}
输出57
对于集合或数组选择,如collection。?〔this4〕将选择出集合元素值大于4的所有元素。选择表达式必须返回布尔类型,使用this表示当前元素。2。测试字典MapString,IntegermapnewHashMapString,Integer();map。put(a,1);map。put(b,2);map。put(c,3);EvaluationContextcontext2newStandardEvaluationContext();context2。setVariable(map,map);MapString,Integerresult2parser。parseExpression(map。?〔key!a〕)。getValue(context2,Map。class);result2。forEach((key,value){System。out。println(key:value);});System。out。println();ListIntegerresult3parser。parseExpression(map。?〔key!a〕。!〔value1〕)。getValue(context2,List。class);result3。forEach(System。out::println);
输出b:2c:334
对于字典选择,如map。?〔this。key!a〕将选择键值不等于a的,其中选择表达式中this是Map。Entry类型,而最终结果还是Map,这点和投影不同;集合选择和投影可以一起使用,如map。?〔key!a〕。!〔value1〕将首先选择键值不等于a的,然后在选出的Map中再进行value1的投影。
表达式模板
模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由前缀表达式后缀形式组成,如{12}即表达式块。在前边我们已经介绍了使用ParserContext接口实现来定义表达式是否是模板及前缀和后缀定义。在此就不多介绍了,如Error{v0}{v1}表达式表示由字面量Error、模板表达式v0、模板表达式v1组成,其中v0和v1表示自定义变量,需要在上下文定义。
解析表达式的时候需要指定模板,模板通过ParserContext接口来定义publicinterfaceParserContext{是否是模板booleanisTemplate();模板表达式前缀StringgetExpressionPrefix();模板表达式后缀StringgetExpressionSuffix();}
有个子类,我们直接可以拿来用:TemplateParserContext。Testpublicvoidtest11(){创建解析器SpelExpressionParserparsernewSpelExpressionParser();创建解析器上下文ParserContextcontextnewTemplateParserContext({,});Expressionexpressionparser。parseExpression(你好:{name},我们正在学习:{lesson},context);创建表达式计算上下文EvaluationContextevaluationContextnewStandardEvaluationContext();evaluationContext。setVariable(name,我是小菠菜);evaluationContext。setVariable(lesson,spring高手系列!);获取值Stringvalueexpression。getValue(evaluationContext,String。class);System。out。println(value);}
运行输出你好:我是小菠菜,我们正在学习:spring高手系列!在Bean定义中使用spel表达式
xml风格的配置
SpEL支持在Bean定义时注入,默认使用{SpEL表达式}表示,其中root根对象默认可以认为是ApplicationContext,只有ApplicationContext实现默认支持SpEL,获取根对象属性其实是获取容器中的Bean。
如:beanidworldclassjava。lang。Stringconstructorargvalue{World!}beanbeanidhello1classjava。lang。Stringconstructorargvalue{Hello}{world}beanbeanidhello2classjava。lang。Stringconstructorargvalue{Helloworld}beanbeanidhello3classjava。lang。Stringconstructorargvalue{Helloworld}bean
模板默认以前缀{开头,以后缀}结尾,且不允许嵌套,如{Hello{world}}错误,如{Helloworld}中world默认解析为Bean。当然可以使用bean引用了。
是不是很简单,除了XML配置方式,Spring还提供一种注解方式Value,接着往下看吧。
注解风格的配置
基于注解风格的SpEL配置也非常简单,使用Value注解来指定SpEL表达式,该注解可以放到字段、方法及方法参数上。
测试Bean类如下,使用Value来指定SpEL表达式:publicclassSpELBean{Value({Helloworld})privateStringvalue;}
在Bean定义中SpEL的问题
如果有同学问{我不是SpEL表达式}不是SpEL表达式,而是公司内部的模板,想换个前缀和后缀该如何实现呢?
我们使用BeanFactoryPostProcessor接口提供postProcessBeanFactory回调方法,它是在IoC容器创建好但还未进行任何Bean初始化时被ApplicationContext实现调用,因此在这个阶段把SpEL前缀及后缀修改掉是安全的,具体代码如下:ComponentpublicclassSpelBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{BeanExpressionResolverbeanExpressionResolverbeanFactory。getBeanExpressionResolver();if(beanExpressionResolverinstanceofStandardBeanExpressionResolver){StandardBeanExpressionResolverresolver(StandardBeanExpressionResolver)beanExpressionResolver;resolver。setExpressionPrefix({);resolver。setExpressionSuffix(});}}}
上测试代码ComponentpublicclassLessonModel{Value(你好,{name},{msg})privateStringdesc;OverridepublicStringtoString(){returnLessonModel{descdesc};}}
name:容器中name的bean
msg:容器中msg的bean
下面我们来个配置类,顺便定义name和msg这2个bean,顺便扫描上面2个配置类ComponentScanConfigurationpublicclassMainConfig{BeanpublicStringname(){return帅哥;}BeanpublicStringmsg(){return欢迎和我一起做学习笔记;}}
测试用例Testpublicvoidtest12(){AnnotationConfigApplicationContextcontextnewAnnotationConfigApplicationContext();context。register(MainConfig。class);context。refresh();LessonModellessonModelcontext。getBean(LessonModel。class);System。out。println(lessonModel);}
运行输出LessonModel{desc你好,帅哥,欢迎和我一起做学习笔记!}总结Spel功能还是比较强大的,可以脱离spring环境独立运行spel可以用在一些动态规则的匹配方面,比如监控系统中监控规则的动态匹配;其他的一些条件动态判断等等本文内容比较长,建议大家把案例都敲一遍,可以设置一些断点去研究一下源码,有问题的,欢迎大家留言交流。
身价仅次于世界首富马斯克!日赚17。5亿,从事的是淘汰行业他在福布斯实时富豪榜净资产901亿美元,他的身价仅次于世界首富马斯克!他是亚洲财富第一人,2021他执掌着全球最大的煤炭资源。他凭借即将淘汰的煤炭一度成为全球第二富豪,为什么靠的是
女人的自律最顶级,有空要多挣钱没事要早睡觉女人一定先要学会自律,要有时间安排和管理,才能学会赚钱的意义和目的!首先我们每天都要做个计划表。可以精确到几点起床几点上班一系列的安排事项!接下来跟着我一起自律起来吧第一个晚睡会掉
冷冻的馒头不能吃?保存超过3天会滋生黄曲霉素,真的是如此吗馒头是中国传统面食之一,以小麦面粉为主要原料,是一种用发酵的面蒸成的食品,尤其是对于北方人来说,是每天都要吃的主食。有时候为了方便,会蒸好多馒头或者买许多馒头,放在冰箱里慢慢吃,随
每天坚持一些好习惯,身体越来越健康每天坚持喝1杯水水是生命之源!喝水是最简单最便宜的养生方式,也是让皮肤更光滑的保养方式。人体中70都是水分,如果缺水的话,就会引起皮肤干燥便秘等不适症状。所以一定要保证充足的饮水。
为什么说人过七十不上坟(1)古人认为人到了七十岁,身体就开始走下坡路了,所以,这一年不能去上坟,不然会有不吉利的事情发生。今天,我就跟大家分享一下这句俗语人过七十不上坟,意思是说过了70岁,身体各方面的机能开
如何快速入睡,避免失眠?很多人经常和我反馈晚上失眠睡不着,焦虑,白天无精打采,到了晚上又精神抖擞,并且周而复始,异常痛苦。失眠这个事情,对想进行自我提升改变的朋友来说是头号大敌,精神不好,什么事情都不想做
长寿秘诀生活中长寿老人的身上都有的特征,你都知道吗?对于大多数人来说,通常应该注意身体的医疗保健,这样疼痛并不容易,它带来了健康的痛苦。老年人随着年龄的增长而逐渐下降,免疫力下降,很容易患病。许多疾病将引发一系列不利症状在开发过程中
健康食疗法则之重视食物的GI值没有营养支持就没有治疗,营养补充在癌症患者治疗前治疗中和康复期间同医学治疗是同等重要的。患者康复期饮食主要以均衡补充营养为主,并提高免疫力,这个时期营养补充应多吃高蛋白高能量密度食
纽约时尚摄影师TheoWennerTheoWenner出生于纽约,在曼哈顿和爱达荷州的海利之间长大。他就读于巴德学院,师从著名艺术家斯蒂芬肖尔学习电影和摄影。温纳一丝不苟地拍摄他的朋友家人和周围环境,这种个人的亲密
谷爱凌穿露脐装大秀腹肌!画烟熏妆气场强大,当众脱鞋与人热聊近日,法国巴黎举办时装周,众多名人受邀参加,我们的青蛙公主谷爱凌与某奢侈品牌有合作,当天也现身秀场,引起网友热议。可以看到,谷爱凌当天罕见画了烟熏妆,穿着半截上衣大秀腹部肌肉,下半
了解紫外线的敌人阻断剂之防晒霜紫外线阻断剂是什么?紫外线阻断剂是指当日光照射到含有这类物质的皮肤表面时,它使紫外线散射,从而阻止了紫外线的射入从而可以保护皮肤免受紫外线伤害的产品的总称。可以防御紫外线引起的皮肤