写点什么

Spring 中的动态表达式 SpEL

作者:豆芽发达咯
  • 2024-08-27
    陕西
  • 本文字数:3103 字

    阅读完需:约 10 分钟

Spring中的动态表达式SpEL

Spring 中的动态表达式艺术:SpEL

由浅入深、从应用到实战,逐步学习理解和掌握 Spring Expression Language。通过学习能够在实际开发过程中灵活应用、扩展以及排查和定位相关问题。

一. 先认眼熟,瞅瞅这玩意是个啥ʕ•̀ω•́ʔ✧

注意 EpEL 表达式以#开头。可以访问属性、调用属性支持的大部分方法、应用计算等


    //自定义了PropertiesFactoryBean,装载了一批属性,等于重命名了    @Value("#{applicationProperties['ris.enable']?:false}")    private Boolean flagRis;    @Value("#{applicationProperties['ris.baseUrl']}")    private String baseUrl;    //做了一个算术运算    @Value("#{${router.times} + 2}")    private String routerTimes;    //取bean的某个属性,public的id,或者private的getId()    @Value("#{transportEntity.id}")    private String transportId;        //system打头的默认只能获取环境变量、系统属性。而environment都可以,即使自定义属性文件只要加载进来即可访问    @Value("#{environment['server.port']?:80}")    private int serverPort;    @Value("#{systemProperties['server.port']?:80}")    private int serverPort2;    @Value("#{systemEnvironment['server.port']?:80}")    private int serverPort3;    //以下两个的获取范围是environment    @Value("#{${server.port}?:80}")//如果没有server.port这个属性,会直接报错    private int serverPort4    @Value("${server.port:80}")    private int serverPort5;        //当某个表达式满足条件是创建bean    @Bean    @ConditionalOnExpression("#{${business.maxCount:0} gt 0}")    public BusinessFood businessFood(){      return new BusinessFood();    }    
复制代码


以下这种是属性占位符,以 $开头,不能包含 SpEL 表达式。里面只能包含 OGNL 表达式访问属性,不能调用方法。


    //这是属性表达式。助理属性表达式占位符中不能包含SpEL表达式,它只能获取属性配置    @Value("${doc.online.appId:}")    private String appId;
复制代码

介绍

Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持运行时查询和操作对象图。语法类似于 Jakarta 表达式语言,但提供了其他功能,最明显的是方法调用和基本字符串模板功能。而且可以独立使用,不过需要创建一些引导基础结构类,例如解析器。但是使用 Spring 环境是开箱即可食用。


  • 表达式语言支持很多功能:诸如访问 字面量、属性、数组、列表、map、变量、方法、正则表达式、类型表示、字符串操作、Bean 访问等

  • 关系运算符:and, or, not, &&, ||, !

  • 逻辑运算符:<, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge

  • 算数运算符:+, -, *, /, %, ^, div, mod、++、--

  • 条件表达式:?:

二. 实际哪里会用到(•ө•)♡

  • 配置文件中注入属性,比如使用 Spring 的 xml 配置、Springboot 的 @Value 中访问

  • 在配置文件中使用它,ExcelUtils 是一个简单的工具类,里面有个静态的 get 方法.(注意调用非 Bean 的静态累的表达式语法是 T(xxx) )

  • 在 Springboot 中使用它

  • 依赖注入和按条件配置,比如上面的@ConditionalOnExpression("#{${business.maxCount:0} gt 0}")这一段,就表示${business.maxCount:0}这个属性的类型必须是int(必须是数值类型),gt表示同时它的值必须大于 0,满足条件才会创建对应的 Bean

  • 换一个姿势,诸如 Bean 的时候按照表达式解析动态传入数据:

  • SpEL 在 AOP 中的应用:

  • 常见的 Spring 框架内置的支持 SpEL 的有:@Value、@ConditionalOnExpression、@ConditionalOnProperty<本身基于属性,但我们可以使用 SpEL 做增强判断等>、Spring Data JPA 的查询、Spring Security 中的表达式等。其他一些基于 Spring 的扩展也会支持 SpEL。

三. 都有哪些使用姿势,支持哪些操作解析,有何限制( ◠‿◠ )

  1. 直接在 Spring 中使用表达式获取符号 #{}操作符来操作元素。如 第一部分展示的那样。

  2. 使用 SpEL API 来手动编写解析,使用内置的解析器和处理器。

  3. 支持的功能列表:

  1. 局限性:SpEL 内置 SpEL Compilation 对表达式进行解释、编译,但不是所有类型的表达式都支持编译。主要关注的是可能在性能关键上下文中使用的常用表达式。无法编译的比如涉及赋值的表达式、自定义解析器或者访问器的表达式、使用重载运算符的表达式、使用数组构造语法的表达式、使用选择或投影的表达式。什么意思呢,其实就是说这些表达式不支持编译运行,这些表达式的性能就不如其他的可编译执行的表达式性能好。和我们 JVM 的编译执行、解释执行一个道理,各有千秋。

  2. 它是一个强大的表达式语言,用来做一些基于配置的动态判断,不复杂的逻辑判断等,不是让编程人员在 SpEL 里面去写业务逻辑或者复杂操作的,应当避免这种行为。

  3. 一般来说,应该避免在 SPEL 表达式中包含用户输入。如果用户输入必须包含在表达式中,那么应该在有限的上下文中进行计算,不允许任意调用方法。【避免使用表达式攻击系统】

四. 还可以手动调用(´◔౪◔)ʃ

//定义复杂表达式要用到的对象TransportEntity entity = new TransportEntity();entity.setId(8);entity.setTransportName("SpaceX");//定义转换器ExpressionParser expressionParser = new SpelExpressionParser();//定义表达式,并解析表达式,返回可重复求值的表达式对象Expression expression = expressionParser.parseExpression("transportName.concat(' lunch')");//定义表达式评估上下文,也就是在此上下文执行设值.这里使用的标准表达式上下文,如果是纯运算等简单的,还可以直接使用SimpleEvaluationContextEvaluationContext context = new StandardEvaluationContext(entity);//通过提供的上下文,计算表达式并返回结果String result = (String) expression.getValue(context);//输出SpaceX lunchExpression expression1 = expressionParser.parseExpression("id >1?100:0");System.out.println(expression1.getValue(context));//输出100
复制代码

五. 噢哟,不错哦!它怎么做到的呢(✿╹◡╹)

  • 解析的总体过程如下:


  • SimpleevalationContext 被设计为仅支持 SpEL 语言语法的一个子集。它排除了 Java 类型引用、构造函数和 bean 引用。它还要求显式地选择对表达式中的属性和方法的支持级别。默认情况下,create ()静态工厂方法只允许对属性进行读访问。StandardEvaluationContext 是完整版的支持,公开全套 SpEL 语言特性和配置选项。可以使用它来指定默认的根对象,并配置每个可用的计算相关策略。(Bean 的引用默认实现使用 JavaBean 约定)

  • 默认情况下,SpEL 使用 Spring 核心中提供的转换服务(org.springframework.core.Convert)。此转换服务附带了许多用于常见转换的内置转换器。此外,它还支持泛型。这意味着,在表达式中处理泛型类型时,SpEL 尝试转换来维护它遇到的任何对象的类型正确性

  • 通过 conversionService.canConvert(sourceTypeDesc, typeDescriptor)来判断是否能将源数据类型转换为目标类型,比如最常见的就是 String 转为 int(使用的 StringToNumberConverterFactory 中的静态内部类 StringToNumber)(使用 NumberUtils.parseNumber 方法)


内置的类型转换器都在 spring-core 核心包的 org.springframework.core.convert 包里面。表达式的定义和解析在 spring-expression 包中。

六. 类似的表达式语言还蛮多哟(๑◔‿◔๑)

Java 表达式语言(OGNL、MVEL 和 JBoss EL 等){不要忘记我们刚开始后端写 jsp 经常用到的 EL 表达式哦,由 web 容器实现并解析和展示,比如我们经常内置 Tomcat 就会看到 tomcat-embed-el-xxx.jar,独立的 Tomcat 在 lib 目录里面的 jasper-el.jar,就是用来把我们在 jsp 页面上写的表达式经过 japer-el 引擎转为字面量展示}

点这里看SpEL官网内容介绍



复制代码


发布于: 刚刚阅读数: 4
用户头像

从哪里跌倒,在哪里休息一会,孟起会再倒下 2019-05-29 加入

还未添加个人简介

评论

发布
暂无评论
Spring中的动态表达式SpEL_SpEL表达式_豆芽发达咯_InfoQ写作社区