Spring 核心特性之表达式(SpEL)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 前言Spring Framework 主要有 9 个核心特性,包括 IoC 容器、事件、资源、国际化、校验、数据绑定、类型转换、表达式以及 AOP。可以说,表达式是最没有存在感的核心特性了,用户直接使用的场景实在太少,这也是我一直没有提及它的原因。不过项目中确实有使用到它的地方,恰好最近整理 Spring 核心特性,为了知识结构完整性姑且总结一篇。

前言


Spring Framework 主要有 9 个核心特性,包括 IoC 容器、事件、资源、国际化、校验、数据绑定、类型转换、表达式以及 AOP。可以说,表达式是最没有存在感的核心特性了,用户直接使用的场景实在太少,这也是我一直没有提及它的原因。不过项目中确实有使用到它的地方,恰好最近整理 Spring 核心特性,为了知识结构完整性姑且总结一篇。


认识 SpEL


Spring 表达式即 Spring Expression Language,简称 SpEL,用于在运行时获取或设置表达式的值。


在 Java 中还有一些其他的表达式语言,如 OGNL、MVEL、JBoss EL 等,Spring 表达式与这些表达式语言在功能上基本类似,它的存在主要为了与 Spring 生态整合。


不过由于 Spring 表达式的 API 设计是中立的,不直接与 Spring 绑定,因此需要的话也可以集成其他的表达式语言实现。


使用 SpEL


SpEL 中有一些概念,需要在使用前理解。


1. 表达式字符串


表达式字符串是字符串形式的表达式,具有特定的语法,是用户直接接触最多的部分,如使用 'Hello,SpEL' 表示字符串 Hello,SpEL。


2. 表达式解析器


表达式解析器用于将字符串形式的表达式解析为用 Expression 对象表示的表达式。使用接口 ExpressionParser 表示,常用的实现为 SpelExpressionParser。


3. 解析上下文


解析上下文用于解析表达式时提供附加的信息,如表达式中是否存在模板。使用接口 ParserContext 表示,常用实现为 TemplateParserContext。


4. 表达式 Expression


表达式 Expression 是表达式解析器 ExpressionParser 解析表达式字符串的结果,用于获取或设置表达式的值。常用实现为 SpelExpression。


5. 评估上下文


评估上下文目的是在 Expression 获取表达式值时提供一些附加信息,例如表达式表示对象的属性时,评估表达式可设置属性所属对象。在 Spring 中使用接口 EvaluationContext 表示,常用实现为 StandardEvaluationContext。


假定我们有对象如下。


@Data
public class User {
    private String name;
    private String age;
}


我们通过表达式获取 name 属性值的方式如下。


public class Application {
    public static void main(String[] args) {
        User user = new User();
        user.setName("hkp");
        String expressionString = "#{name}";
        ParserContext parserContext = new TemplateParserContext("#{", "}");
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expressionString, parserContext);
        EvaluationContext evaluationContext = new StandardEvaluationContext(user);
        String name = expression.getValue(evaluationContext, String.class);
        System.out.println(name);
    }
}


SpEL 实现


SpEL 在 Spring 内部的实现可以简单理解如下:


首先用户调用 ExpressionParser#parseExpression 方法触发表达式解析。

表达式解析器在内部先进行词法解析,将字符串形式的表达式拆分成不同的 Token,如 1 + 2 表达式会被拆分成 1、+、2 三部分。解析时同时会参考上下文 ParserContext,如上述示例中的 #{name} 表达式,解析器会先去掉前后缀#{},然后再进行解析。

随后 Token 将被转换为抽象语法树,在内部使用 SpelNode 表示,为了简化用户操作语法树被包装到 Expression。

用户使用 Expression#getValue 方法获取表达式的值,在内部也会参考评估上下文 EvaluationContext 进行解析,例如上述示例中设置的根对象 user。


SpEL 语法


SpEL 语法支持的功能丰富多彩,常见语法如下。


1. 字面量表达式


字面量表达式支持字符串、数值(整型、浮点数、16进制)、boolean 和 null,其中字符串使用单引号'表示,如果字符串内包含单引号',可以使用两个单引号。


示例如下:


字符串:Hello World!

浮点数:6.0221415E+23

16 进制数:0x7FFFFFFF

boolean 类型:true

null 值:null


2. 属性引用


属性引用允许访问普通对象、数组、集合、Map 包含的属性值。


普通对象:普通对象的属性引用直接使用属性名即可,如果遇到嵌套属性,可以使用 . 表示,并且属性名称的抵押给字母不区分大小写,如 Birthdate.Year + 1900。

数组、集合:使用中括号 [index] 的形式引用,如 Members[0].Inventions[6]。

Map:使用中括号 ['key'] 的形式引用,如 Officers['advisors'][0].PlaceOfBirth.Country。


3. 方法调用


使用 Java 语法即可进行方法调用,如 'abc'.substring(1, 3)。


4. 运算符


SpEL 支持关系运算符、逻辑运算符、数据运算符、赋值运算符。可以使用常见字符表示,也可以使用对应的文本表示,如 eq 等同于 ==,如果使用文本表示则不区分大小写。


4.1 关系运算符


包括常见的等于:==(eq)、不等于:!=(ne)、小于:<(gt)、小于等于:<=(le)、大于:>(gt)、大于等于>=(ge),此外还支持判断类型的 instanceOf 及正则判断的 matches 运算符。示例如下:


2 = 2

'xyz' instanceof T(Integer)

'5.00' matches '^-?\\d+(\\.\\d{2})?$'


4.2 逻辑运算符


包括与:&&(and)、或:||(or)、非:!(not),如 true and false。



4.3 数学运算符


数学运算符可用于数字和字符串,对于数字可使用加:+、减:-、乘:*、除:/、取模:%、指数幂:^,多个数学运算符按照标准的运算符优先级。示例如下:


1 + 1

'test' + ' ' + 'string'


4.4 赋值运算符


赋值运算符为 =,用于 Expression#setValue 或 Expression#getValue 方法调用设置表达式的值。示例如下。


public class Application {
    public static void main(String[] args) {
        User user = new User();
        user.setName("hkp");
        String expressionString = "age = 18";
        Expression expression = new SpelExpressionParser().parseExpression(expressionString);
        EvaluationContext evaluationContext = new StandardEvaluationContext(user);
        expression.getValue(evaluationContext, Integer.class);
        System.out.println(user.getAge());
    }
}


5. 类型


可以使用 T(classname) 的形式表示 java.lang.Class 的实例,如果包名为 java.lang 可以忽略包名,如 T(java.util.Date)、T(String)。

也可以使用 T(classname) 调用静态方法,如 T(String).valueOf(123)。


6. 构造方法


可以通过使用 new 关键字调用构造方法,注意除了 String 应该使用完整限定名,如 new String('abc')。


7. 变量


可以使用 #variableName 的形式引用变量,变量在 EvaluationContext#setVariable 上进行设置,变量名只能包含字母 A到Z,a 到z、数字 0 到 9、下划线 _ 以及美元符号 $。变量使用示例如下。


public class Application {
    public static void main(String[] args) {
        User user = new User();
        user.setName("hkp");
        String expressionString = "age = #age";
        Expression expression = new SpelExpressionParser().parseExpression(expressionString);
        EvaluationContext evaluationContext = new StandardEvaluationContext(user);
        evaluationContext.setVariable("age", 18);
        expression.getValue(evaluationContext, Integer.class);
        System.out.println(user.getAge());
    }
}


8. 三元运算符


SpEL 同样支持三元运算符 ?:,如 false ? 'trueExp' : 'falseExp'。


此外还支持使用 ?: 语法对三元运算符进行简化,如 name != null ? name : 'Unknown' 可以简化为 name?:'Unknown'。


9. 安全导航运算符


安全导航运算符即 ?.,用于避免在访问对象的属性或方法前判断对象不为空,以免抛出 NullPointerException 异常,如 PlaceOfBirth?.City。


10. 表达式模板


表达式模板用于将文本和其他要评估的表达式混合到一起,评估表达式需要包含在前缀和后缀中,前后缀通常为 #{ 和 },示例如下。


public class Application {
    public static void main(String[] args) {
        String expressionString = "random number is #{T(java.lang.Math).random()}";
        ParserContext parserContext = new TemplateParserContext("#{", "}");
        Expression expression = new SpelExpressionParser().parseExpression(expressionString, parserContext);
        String value = expression.getValue(String.class);
        System.out.println(value);
    }
}


SpEL 使用场景


我总结了 SpELl 日常在项目中的两个应用场景。


依赖注入


可以使用 @Value 注入表达式的值,示例如下。


public class PropertyValueTestBean {
    private String defaultLocale;
    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }
    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}


其中 systemProperties 是内置的表示系统属性的变量。


缓存 key 动态获取


另一种应用场景是可以使用 AOP 拦截方法的执行,使用方法参数作为缓存或分布式锁的 key,代码如下。


@Aspect
@Component
public class LockAspect {
    @Around("@annotation(concurLock)")
    public Object around(ProceedingJoinPoint joinPoint, ConcurLock concurLock) throws Throwable {
        // 获取方法参数名并设置到 EvaluationContext 变量中
        EvaluationContext context = new StandardEvaluationContext();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String[] parametersNames = new DefaultParameterNameDiscoverer().getParameterNames(signature.getMethod());
        for (int i = 0; i < args.length; i++) {
            context.setVariable(parametersNames[i], args[i]);
        }
        // 解析表达式作为缓存 key
        String lockKey = new SpelExpressionParser().parseExpression(concurLock.key()).getValue(context, String.class);
        ...省略缓存相关代码
        return joinPoint.proceed();
    }
}
目录
相关文章
|
1月前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
33 1
|
18天前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
72 6
|
21天前
|
存储 Java 开发者
使用Spring Boot 3.3全新特性CDS,启动速度狂飙100%!
【8月更文挑战第30天】在快速迭代的软件开发周期中,应用的启动速度是开发者不可忽视的一个重要指标。它不仅影响着开发效率,还直接关系到用户体验。随着Spring Boot 3.3的发布,其中引入的Class Data Sharing(CDS)技术为应用的启动速度带来了革命性的提升。本文将围绕这一全新特性,深入探讨其原理、使用方法以及带来的实际效益,为开发者们带来一场技术盛宴。
40 2
|
1月前
|
XML Java 应用服务中间件
深入探索Spring Boot框架的核心特性
Spring Boot 是一款基于Spring框架的开源框架,旨在简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式(默认配置)来简化整个构建过程。
44 11
|
30天前
|
Java 开发者 Spring
|
2月前
|
Java 应用服务中间件 开发者
Spring Boot 2.x新特性有哪些?
【7月更文挑战第16天】Spring Boot 2.x新特性有哪些?
39 1
|
3月前
|
前端开发 安全 Java
Spring EL表达式:概念、特性与应用深入解析
Spring EL表达式:概念、特性与应用深入解析
|
3月前
|
XML Java 数据库
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
23 0
|
3月前
|
监控 Java 开发者
Spring Boot 3 升级全解析:新特性与改进点一网打尽
Spring Boot 3 升级全解析:新特性与改进点一网打尽