Spring Expression Language(SpEL) 4 学习笔记

前言

时隔多年重新开始学习Spring,之前只用过Spring 2,掰掰指头已经过了快10年了。
看到书里提到了SpEL,一头雾水,在2的时代好像从来没见过。把学习到的做个总结。

SpEL概述

SpEL是Spring内置的表达式语言,语法与OGNL等其他表达式语言十分类似。SpEL设计之初就是朝着做一个表达式语言的通用框架,可以独立运行。

SpEL的主要相关类

SpEL对表达式语法解析过程进行了很高的抽象,抽象出解析器、表达式、解析上下文、估值(Evaluate)上下文等对象,非常优雅的表达了解析逻辑。主要的对象如下:

类名 说明
ExpressionParser 表达式解析器接口,包含了(Expression) parseExpression(String), (Expression) parseExpression(String, ParserContext)两个接口方法
ParserContext 解析器上下文接口,主要是对解析器Token的抽象类,包含3个方法:getExpressionPrefix,getExpressionSuffixisTemplate,就是表示表达式从什么符号开始什么符号结束,是否是作为模板(包含字面量和表达式)解析。一般保持默认。
Expression 表达式的抽象,是经过解析后的字符串表达式的形式表示。通过expressionInstance.getValue方法,可以获取表示式的值。也可以通过调用getValue(EvaluationContext),从评估(evaluation)上下文中获取表达式对于当前上下文的值
EvaluationContext 估值上下文接口,只有一个setter方法:setVariable(String, Object),通过调用该方法,可以为evaluation提供上下文变量

完整的例子:

public static void main(String[] args) {        
    String greetingExp = "Hello, #{ #user }";                             (1)     
    ExpressionParser parser = new SpelExpressionParser();           (2)
    EvaluationContext context = new StandardEvaluationContext();        
    context.setVariable("user", "Gangyou");        (3)

    Expression expression = parser.parseExpression(greetingExp, 
        new TemplateParserContext());     (4)
    System.out.println(expression.getValue(context, String.class)); (5)
}

代码解释:

  1. 创建一个模板表达式,所谓模板就是带字面量和表达式的字符串。其中#{}表示表达式的起止,#user是表达式字符串,表示引用一个变量。
  2. 创建表达式解析器,SpEL框架创建了一个语言无关的处理框架,所以对于其他的表达式语言,完全可以创建不同的ExpressionParser。在这里我们学习的是SpEL所以使用SpelExpressionParser()
  3. 通过evaluationContext.setVariable可以在上下文中设定变量。
  4. 解析表达式,如果表达式是一个模板表达式,需要为解析传入模板解析器上下文。如果不传入模板解析器上下文,指定表达式为模板,那么表达式字符串Hello, #{ #user },解析器会首先去尝试解析Hello。例子中的模板表达式,与'Hello, ' + #user是等价的。
  5. 使用Expression.getValue()获取表达式的值,这里传入了Evalution上下文,第二个参数是类型参数,表示返回值的类型。

SpEL语法

本节介绍下SpEL中的各类语法,在语法中会发现很多熟悉的影子,比如Java,JavaScript, Groovy等等。依次介绍SpEL中的

  • 字面量
  • 数组和Map字面量
  • 二元操作符和三元操作符
  • 来自Groovy的操作符
  • 数组列表和Map的访问
  • Java Bean属性访问
  • Java Bean方法调用
  • Java 类型访问
  • Java 实例访问

字面量

SpEL支持如下类型的字面量:

类型 说明 示例
数字 支持任何数字类型,包括了各种进制的数字,科学计数法等 6.0221415E+23,0xFFFFFFFF
布尔量 true, false
字符串 用单引号包围的字符串,单引号要用2个单引号转义 'Gangyou''s, Blog'
日期 没写成功 //TODO
null 直接转成字符串null null

属性

SpEL对属性的访问遵循Java Bean语法,对于表达式中的属性都要提供相应的setter。

比如这样一个Bean:

private static class Person {    
    private String firstName;    
    private String lastName;    
    public Person(String firstName, String lastName) {        
         this.firstName = firstName;        
         this.lastName = lastName;    
    }    
    public String getFirstName() {        
        return firstName;    
    }    
    public String getLastName() {        
        return lastName;    
    }
}

表达式firstName + ' ' + lastName就等价于person.getFirstName() + " " + person.getLastName()。如果属性本身是对象,还支持嵌套属性,如person.address.city,就等价于person.getAddress().getCity()

数组、列表和Map

数组和列表都可以用过数字下标进行访问,比如list[0]

Map的访问就类似JavaScript的访问方式,使用key访问。例如java代码map.put("name", "gangyou")其中的map对象,可以通过map['name']获取到字符串gangyou

内联的数组和Map

Spel允许在表达式内创建数组(列表)和Map,如{1,2,3,4},{'firstName': 'Gang', 'lastName': 'You'}

方法调用

SpEL使用java的相同语法进行方法调用,如'Hello.concat(', World!')`,输出** Hello, World!**。

类型

SpEL中可以使用特定的Java类型,经常用来访问Java类型中的静态属性或静态方法,需要用T()操作符进行声明。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL内置了java.lang包下的类声明,也就是说java.lang.String可以通过T(String)访问,而不需要使用全限定名。

两个例子: T(Math).random()T(java.lang.Math).random()

创建实例

使用new可以直接在SpEL中创建实例,需要创建实例的类要通过全限定名进行访问,如:new java.util.Date().now()

二元操作符

SpEL中的二元操作符同Java中的二元操作符,包括了数学运算符、位运算符、关系运算符等等。

三元操作符

SpEL的三元操作符主要是 ** if else then ** 操作符 condition ? true statement : false statement与Java中的一致。

另外一个就是借鉴自Groovy的 Elvis 操作符?:,Elvis就是猫王!

22571c16e399948037ed26756945c6fd.jpg

这个操作符的样子就跟猫王的发型一样:)。

举一个例子

String expressionStr1 = " name != null ? name : 'Default Value'";
String expressionStr2 = "name ?: 'Default Value'";

上面的两个表达式都是先判断 getName()的返回值是不是null,如果是就返回Default Value,通过下面的Elvis操作符可以让代码更加的清晰。

安全访问符

同样借鉴自Groovy,在SpEL中引入了安全访问符Safe Navigator Operator——.?,解决了很大问题。相信每个Javaer都遇到过NullPointException的运行时异常,通常是对象还未实例化或者找不到对象,却访问对象属性造成的。通过安全访问符就可以避免这样的问题。

String expStr = "thisMayBeNull.?property"

这句表达式在求值的时候,不会因为thisMayBeNull是Null值而抛出NullPointException,而是会简单的返回null。个人认为此处结合Elvis操作符,是一个很完善的处理方式。

集合选择

同样借鉴自Groovy,SpEL提供了集合选择语法,如下面的例子:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);

如果Members List不是Null,则在列表中选择getNationality() == 'Serbian'的对象集合返回。这个很类似于Java 8中的stream and filter方式,只是更加的简洁。

[]中间可以利用任何的布尔表达式,创建筛选条件。例如age > 18name.startsWith('You')等等。

自定义函数

SpEL提供了Java的基础功能,也引入了3个借鉴自Groovy的特性语法提供更为简洁的表达能力。作为一款设计为框架的语言,更提供了自定义的扩展能力。

比如下面的例子:


public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder();
        for (int i = 0; i < input.length(); i++)
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }

    public static void main(String[] args){
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();

        context.registerFunction("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));

        String helloWorldReversed = parser.parseExpression(
            "#reverseString('hello')").getValue(context, String.class);
    }
}

通过使用StandardEvalutinContext.registerFunction可以注册自定义的函数,唯一的一点要求就是需要在表达式中通过#注册函数名的方式引用函数。

参考资料

Spring Expression Language 官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,171评论 9 118
  • 意境, 方为四大皆空, 处处精致, 一丝淡泊与宁静的心境。 禅意空间 云水禅心,是自我的修行; 杯盏人生,是灵魂的...
    谷丰书画陶刻艺术论坛阅读 473评论 0 0
  • 不愿意与人分享快乐的事 源自于不愿与人分享痛苦的事 首先我相信一点 世界上没有感同身受 同情对于没经历过的人很容易...
    LUPOU阅读 1,300评论 0 0