SpringBoot技术实践-SpEL&EL表达式

2022-07-19 08:17:19

一、SpringEL-基础介绍

  1. 什么是SpringEL(SpEL)?
    • Spring3中引入了Spring表达式语言—SpringEL,SpEL是一种强大,简洁的装配Bean的方式
    • SpringEL可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中
    • SpringEL可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置
  2. 为什么要使用SpringEL?
    • 平常通过配置文件或Annotaton注入的Bean,其实都可以称为静态性注入
    • 如Bean A中有变量A,它的值需要根据Bean B的B变量为参考,在这场景下静态注入就对这样的处理显得非常无力
    • 而Spring3增加的SpringEL就可以完全满足这种需求,而且还可以对不同Bean的字段进行计算再进行赋值,功能非常强大
  3. 如何使用SpringEL?
    • SpringEL从名字来看就能看出和EL是有点关系的,SpringEL的使用和EL表达式的使用非常相似
    • EL表达式在JSP页面更方便的获取后台中的值,而SpringEL就是为了更方便获取Spring容器中的Bean的值
    • EL使用${},而SpringEL使用#{}进行表达式的声明
  4. 两者主要区别
    • $是去找外部配置的参数,将值赋过来
    • #是SpEL表达式,去寻找对应变量的内容
    • 也可以直接使用@value(“常量”)注入不使用EL,这样写法与直接赋值等价
  5. 如果是在Spring中使用可以使用**@PropertySource(“classpath:my.properties”)**加载对应配置文件

二、EL表达式-基础使用

# 配置文件com:codecoord:el:num:1001name: ellanguage:- java- spring- mysql- linux# 逗号分隔可以注入列表language02: java,spring,mysql,linux
  1. 使用EL注入简单值
/**
 * 注入简单值,直接注入不使用EL,EL不支持直接指定常量
 * 直接在EL中指定的常量会当做配置处理,和直接赋值等价
 */@Value("1432516744")private Integer no;
  1. 注入配置文件属性值
/**
 * 注入整型属性值
 */@Value("${com.codecoord.el.num}")private Integer num;/**
 * 注入字符属性值
 */@Value("${com.codecoord.el.name}")private String name;
  1. 注入默认值
/**
 * 注入字符不存在属性值并指定默认值,默认值使用过冒号分隔 :
 * 注入常量其实就可以指定一个不存在的配置然后使用默认值,此处skill的值为java
 */@Value("${com.codecoord.el.skill:java}")private String skill;
  1. 注入列表
    • 不支持直接配置文件中数组语法格式注入列表
    • 可以识别使用逗号,分隔的配置,spring默认以,分隔
// 错误写法:不支持直接注入yml列表格式语法列表@Value("${com.codecoord.el.language}")private List<String> listLanguage;@Value("${com.codecoord.el.language}")private String[] strLanguage;
/**
 * 支持,分隔的注入列表
 */@Value("${com.codecoord.el.language02}")private List<String> listLanguage02;@Value("${com.codecoord.el.language02}")private String[] strLanguage02;
  1. 完整参考如下
    • 配置文件
server:port:8888com:codecoord:el:num:1001name: ellanguage:- java- spring- mysql- linux# 逗号分隔可以注入列表language02: java,spring,mysql,linux
  • 属性配置类
import lombok.Data;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import java.util.List;@Data@ComponentpublicclassElConfig{/**
     * 注入简单值,直接注入不使用EL,EL不支持直接指定常量
     * 直接在EL中指定的常量会当做配置处理,和直接赋值等价
     */@Value("1432516744")private Integer no;/**
     * 注入整型属性值
     */@Value("${com.codecoord.el.num}")private Integer num;/**
     * 注入字符属性值
     */@Value("${com.codecoord.el.name}")private String name;/**
     * 注入字符不存在属性值并指定默认值,默认值使用过冒号分隔 :
     * 注入常量其实就可以指定一个不存在的配置然后使用默认值,此处skill的值为java
     */@Value("${com.codecoord.el.skill:java}")private String skill;/// 不支持直接注入列表/*@Value("${com.codecoord.el.language}")
    private List<String> listLanguage;
    @Value("${com.codecoord.el.language}")
    private String[] strLanguage;*//**
     * 支持,分隔的注入列表
     */@Value("${com.codecoord.el.language02}")private List<String> listLanguage02;@Value("${com.codecoord.el.language02}")private String[] strLanguage02;}
  • 向controller中注入配置类,然后访问接口测试结果如下
{"no":1432516744,"num":1001,"name":"el","skill":"java","listLanguage02":["java","spring","mysql","linux"],"strLanguage02":["java","spring","mysql","linux"]}

三、SpringEL-基础使用

  1. 使用SpEL注入简单值和普通EL注入使用基本一致
  2. SpEl注入map
    • 配置文件中需要使用双引号括起来,否则将会注入失败,key为单引号
# SpElspEl:mapInject:"{'name': 'SpEl', 'website': 'http://www.codeocord.com'}"
  • java类中先使用${spEl.mapInject}注入字符串值,#{}会解析字符串的值转为map
@Value("#{${spEl.mapInject}}")private Map<String, String> mapInject;
  1. SpEl注入list
    • 除了可以通过EL注入listI外,也可以使用#{${}.split(‘分隔符’)}的方式注入List
    • 配置文件中例如使用#分隔
spEl:listInject:"44#11#99#100"
  • java类中先使用${spEl.listInject}注入字符串值,内容使用单引号括起来,然后对字符串使用split方法分隔
  • 提示:避免为空情况,可以给一个默认值空串
@Value("#{'${spEl.listInject:}'.split('#')}")private List<String> listInject;
  1. 动态注入
    • 上述注入都是静态注入,SpEl支持从Spring容器中注入信息,称为动态注入。动态注入类如下
import lombok.AllArgsConstructor;import lombok.Data;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@Component@DatapublicclassSpElConstant{private String name="SpElConstant-name";private String nickname="tianxin";privateint num=100;private List<String> product=newArrayList<String>(){{add("huaweiMate30Pro");add("xiaomi10x5g");}};private Map<String, String> productMap=newHashMap<String, String>(){{put("huaweiMate30Pro","5999");put("xiaomi10x5g","4999");}};private List<City> cityList=newArrayList<City>(){{add(newCity("深圳",1000L));add(newCity("杭州",2000L));add(newCity("贵阳",900L));}};public StringshowProperty(){return"showProperty-无参数";}public StringshowProperty(String name){return"showProperty-"+ name;}@Data@AllArgsConstructorstaticclassCity{private String name;privatelong population;}}
  1. SpEl支持和不支持操作
    • 支持动态注入实例,类似于对象自动注入
    • SPL不支持直接注入配置文件中的配置
    • 支持调用静态和实例方法
      • 静态方法:@Value("#{T(package.ClassName).ConstFieldName")
    • 支持调用静态类或常量
    • 支持运算符运算
    • 支持操作集合
    • 支持查询筛选集合和投影
  2. 注入完整操作如下
import lombok.Data;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import java.util.List;import java.util.Map;@Data@ComponentpublicclassSpElConfig{/// 不支持直接注入配置文件值/*@Value("#{com.codecoord.el.num}")
    private Integer num;*//**
     * 对象注入
     */@Value("#{spElConstant}")private SpElConstant spElConstant;/**
     * 注入ID为spElConstant Bean中的STR常量/变量
     */@Value("#{spElConstant.name}")private String name;/**
     * 调用无参方法
     */@Value("#{spElConstant.showProperty()}")private String method1;/**
     * 有参接收字符串的方法
     */@Value("#{spElConstant.showProperty('Hell SpringEL')}")private String method2;/**
     * 方法返回的String为大写
     */@Value("#{spElConstant.showProperty().toUpperCase()}")private String method3;/**
     * 若使用method3这种方式,若果showProperty返回为null
     * 将会抛出NullPointerException,可以使用以下方式避免
     * 使用?.符号代表若然左边的值为null,将不执行右边方法
     */@Value("#{spElConstant.showProperty()?.toUpperCase()}")private String method4;/**
     * 注入math常量
     */@Value("#{T(java.lang.Math).PI}")privatedouble pi;/**
     * 用random方法获取返回值
     */@Value("#{T(java.lang.Math).random()}")privatedouble random;/**
     * 获取文件路径符号
     */@Value("#{T(java.io.File).separator}")private String separator;/**
     * 拼接字符串
     */@Value("#{spElConstant.nickname + ' ' + spElConstant.name}")private String concatString;/**
     * 对数字类型进行运算,spElConstant拥有num属性
     */@Value("#{3 * T(java.lang.Math).PI + spElConstant.num}")privatedouble operation;/**
     * 进行逻辑运算
     */@Value("#{spElConstant.num > 100 and spElConstant.num <= 200}")privateboolean logicOperation;/**
     * 进行或非逻辑操作
     */@Value("#{not (spElConstant.num == 100) or spElConstant.num <= 200}")privateboolean logicOperation2;/**
     * 使用三元运算符
     */@Value("#{spElConstant.num > 100 ? spElConstant.num : spElConstant.num + 100}")private Integer logicOperation3;/**
     * 获取下标为0的元素
     */@Value("#{spElConstant.product[0]}")private String str;/**
     * 获取下标为0元素的大写形式
     */@Value("#{spElConstant.product[0]?.toUpperCase()}")private String upperStr;/**
     * 获取map中key为hello的value
     */@Value("#{spElConstant.productMap['hello']}")private String mapValue;/**
     * 根据product下标为0元素作为key获取testMap的value
     */@Value("#{spElConstant.productMap[spElConstant.product[0]]}")private String mapStrByproduct;/**
     * 注入人口大于等于1000人口的城市
     */@Value("#{spElConstant.cityList.?[population >= 1000]}")private List<SpElConstant.City> cityList;/**
     * 注入人口等于900人口的城市
     */@Value("#{spElConstant.cityList.?[population == 900]}")private SpElConstant.City city;/**
     * 注入人口大于等于1000人口的城市,且只保留城市名称
     */@Value("#{spElConstant.cityList.?[population >= 1000].![name]}")private List<String> cityName;}
  1. 注入结果
{"spElConstant":{"name":"SpElConstant-name","nickname":"tianxin","num":100,"product":["huaweiMate30Pro","xiaomi10x5g"],"productMap":{"xiaomi10x5g":"4999","huaweiMate30Pro":"5999"},"cityList":[{"name":"深圳","population":1000},{"name":"杭州","population":2000},{"name":"贵阳","population":900}]},"name":"SpElConstant-name","method1":"showProperty-无参数","method2":"showProperty-Hell SpringEL","method3":"SHOWPROPERTY-无参数","method4":"SHOWPROPERTY-无参数","pi":3.141592653589793,"random":0.19997238292235787,"separator":"\\","concatString":"tianxin SpElConstant-name","operation":109.42477796076938,"logicOperation":false,"logicOperation2":true,"logicOperation3":200,"str":"huaweiMate30Pro","upperStr":"HUAWEIMATE30PRO","mapValue":null,"mapStrByproduct":"5999","cityList":[{"name":"深圳","population":1000},{"name":"杭州","population":2000}],"city":{"name":"贵阳","population":900},"cityName":["深圳","杭州"]}
  1. Spring操作外部Properties文件
<!-- 首先通过applicaContext.xml中<util:properties>增加properties文件--><!-- 注意需要引入Spring的util schemea命名空间和注意id属性,id属性将在SpringEL中使用--><util:properties id="db" location="classpath:application.properties"/>
publicclassTestSpringEL{// 注意db为xml文件中声明的id@Value("#{db['jdbc.url']}")private String propertiesValue;}
  1. SpringEL在使用时仅仅是一个字符串,不易于排错与测试,也没有IDE检查我们的语法,当出现错误时较难检测,复杂的表达式不建议通过SpringEL方式注入。在非必要情况下,不推荐使用SpEl的复杂注入,清晰可读的代码更为重要且有利于排查问题

四、属性自动注入

  1. 上述都是通过指定字段进行注入,可以通过@ConfigurationProperties指定前缀进行自动注入
    • org.springframework.boot.context.properties.ConfigurationProperties
  2. 配置类
user:id: ${random.uuid}name: autowireaddress: unknownwebsite: www.codecoord.comage: ${random.int}
  1. 自动属性注入类
    • 通过prefix指定前端为user,然后将会把user.后的类型按照名称进行注入
    • 注意必须要提供setter方法
import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component@ConfigurationProperties(prefix="user")@DatapublicclassUserConfig{private String id;private String name;private String address;private String website;private Integer age;}
  1. 可以通过@EnableConfigurationProperties(value = UserConfig.class)将UserConfig再次强制注入,问题出现在如果UserConfig为第三方jar包内的配置类,则可能出现属性没有注入情况,所以可以指定注入
  • 作者:TianXinCoord
  • 原文链接:https://codecoord.blog.csdn.net/article/details/119031948
    更新时间:2022-07-19 08:17:19