12. spring-容器: SpEL详解

上一章讲解了#{…}中可以写SpEL表达式,本章详细讲一讲如何使用。

什么是SpEL

即the Spring Expression Language的缩写,是一个强大的表达式语言,能支持在运行时查询和处理对象图。

它的语法跟jsp中的EL表达式类似,但提供了额外的特性,最显著的是方法调用(method invocation)和基本的字符串处理功能。

除了SpEL, 还有其他类似的框架,如:OGNL,MVEL,JBoss EL。 从SpEL名字可以看出,它创建出来致力于被所有spring产品使用,它的特性由spring生态需求驱动。且SpEL是技术无关的,如果需要也可以允许其他表达式语言框架集成进来。

SpEL完全不依赖Spring执行环境,可当作工具独立使用。

SpEL有哪些特性
  • 文字表达式(Literal expressions)
  • 布尔类型和相关操作(Boolean and relational operators)
  • 正则表达式(Regular expressions)
  • 类表达式(Class expressions)
  • 访问属性,数组,列表和字典(Accessing properties, arrays, lists, and maps)
  • 方法调用(Method invocation)
  • 变量(Variables)
  • … (etc)
如何使用

通常用在三个地方:

  • 在注解@Value中使用
@Value("#{...}")
private String arg;
  • 在XML中配置
<bean id="xxx" class="com.xx.xx.Xxx">
   <property name="arg" value="#{...}"/>
</bean>
  • 在代码中使用Expression
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("...");
Object value = exp.getValue();

具体语法详解

使用时在项目中添加如下依赖(一般会间接依赖进来):

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>${spring.version}</version>
</dependency>
1. 字面量赋值
// 整数
@Value("#{5}")
private Integer arg;

// 浮点数
@Value("#{3.14}")
private Double arg;

// 科学计数法
@Value("#{1e4}")
private Long arg;

// 字符串
@Value("#{'我是字符串'}")
private String arg;

// 布尔值
@Value("#{true}")
private Boolean arg;

注:

  • 字面量赋值必须要和对应属性的类型兼容,否则会报异常。
  • 一般情况下不需要使用SpEL字面量赋值,因为多此一举,可以直接赋值。
2. 引用Bean, 属性和方法(必须是public修饰)
package win.elegentjs.spring.ioc.spel.bean;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

/**
 * 演示SpEL引用Bean,属性和方法
 */
@Data
public class BeanSpEL {

    // 注入bean
    @Value("#{animal}")
    private Animal animal;

    // 注入bean的属性
    @Value("#{animal.name}")
    private String animalName;

    // 通过bean的方法调用注入
    @Value("#{animal.getAge()}")
    private String animalAge;
}

// 测试结果

package win.elegentjs.spring.ioc.spel.bean;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 演示SpEL引用Bean,属性和方法
 */
@Slf4j
public class BeanSpELSample {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        BeanSpEL beanSpEL = context.getBean(BeanSpEL.class);

        log.info("==> beanSpEL: {}", beanSpEL);
    }
}

// result:
2021-06-01 18:53:46.200 [main] INFO  win.elegentjs.spring.ioc.spel.bean.BeanSpELSample-==> beanSpEL: BeanSpEL(animal=Animal(name=fox, age=2), animalName=fox, animalAge=2)

注:直接引用bean的用法不常见,因为使用@Autowired注入更直观。

3. 运算符
3.1 算术运算符: +,-,*,/,%,^
	// 算术运算符:加法
    @Value("#{10 + 1}")
    private Integer num1;

    // 算术运算符:减法
    @Value("#{10 - 1}")
    private Integer num2;

    // 算术运算符:乘法
    @Value("#{10 * 1}")
    private Integer num3;

    // 算术运算符:除法
    @Value("#{10 / 1}")
    private Integer num4;

    // 算术运算符:取余
    @Value("#{10 % 1}")
    private Integer num5;

    // 算术运算符:指数
    @Value("#{10 ^ 3}")
    private Integer num6;
3.2 字符串连接符: +
// 字符串拼接
@Value("#{21 + '年' + 6 + '月'}")
private String str1;
3.3 比较运算符:<, lt, >, gt, =, eq, <=, le, >=, gt
	// 比较运算符 <
    @Value("#{1 < 10}")
    private Boolean oper1;

    // 比较运算符 lt
    @Value("#{1 lt 10}")
    private Boolean oper2;

    // 比较运算符 >
    @Value("#{1 > 10}")
    private Boolean oper3;

    // 比较运算符 gt
    @Value("#{1 gt 10}")
    private Boolean oper4;

    // 比较运算符 ==
    @Value("#{1 == 10}")
    private Boolean oper5;

    // 比较运算符 eq
    @Value("#{1 eq 10}")
    private Boolean oper6;

    // 比较运算符 <=
    @Value("#{1 <= 10}")
    private Boolean oper7;

    // 比较运算符 le
    @Value("#{1 le 10}")
    private Boolean oper8;

    // 比较运算符 >=
    @Value("#{10 >= 1}")
    private Boolean oper9;

    // 比较运算符 ge
    @Value("#{10 ge 1}")
    private Boolean oper10;
3.4 逻辑运算符:and, or, not, &&, ||, !
	// 逻辑运算符 and
    @Value("#{true and false}")
    private Boolean logic1;

    // 逻辑运算符 &&
    @Value("#{true && false}")
    private Boolean logic2;

    // 逻辑运算符 or
    @Value("#{true or false}")
    private Boolean logic3;

    // 逻辑运算符 ||
    @Value("#{true || false}")
    private Boolean logic4;

    // 逻辑运算符 not
    @Value("#{not false}")
    private Boolean logic5;

    // 逻辑运算符 !
    @Value("#{!false}")
    private Boolean logic6;
3.5 三元运算符:? :
// 三元运算
@Value("#{(10 > 3) ? 'YES': 'FALSE'}")
private String condition1;
3.6 正则表达式: matches
@Value("#{'19898990988' matches '\\d+'}")
private Boolean regExp;
4. 调用静态方法或静态属性
@Value("#{T(Integer).MAX_VALUE}")
private Integer intVal;

@Value("#{T(Math).random()}")
private Double pi;
5. 方法调用

使用registerFunction和setVariable方法注册自定义函数。了解即可。

package win.elegentjs.spring.ioc.spel.method;

import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * 演示自定义方法调用示例,向ctx注册了方法parseInt和parseInt2
 * 看看执行效果
 */
@Slf4j
public class MethodInvocationSample {

    public static void main(String[] args) throws NoSuchMethodException {
        //创建ctx容器
        StandardEvaluationContext ctx = new StandardEvaluationContext();

        //获取java自带的Integer类的parseInt(String)方法
        Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);

        //将parseInt方法注册在ctx容器内
        ctx.registerFunction("parseInt", parseInt);

        //再将parseInt方法设为parseInt2
        ctx.setVariable("parseInt2", parseInt);

        //创建ExpressionParser解析表达式
        ExpressionParser parser = new SpelExpressionParser();

        //SpEL语法,比对两个方法执行完成后,结果是否相同
        String expreString = "#parseInt('2') == #parseInt2('3')";
        Expression expression = parser.parseExpression(expreString);
        Boolean result = expression.getValue(ctx, Boolean.class);
        log.info("==> expreString: {}, result: {}", expreString, result);

        expreString = "#parseInt('2') == #parseInt2('2')";
        expression = parser.parseExpression(expreString);
        result = expression.getValue(ctx, Boolean.class);
        log.info("==> expreString: {}, result: {}", expreString, result);
    }
}

// result:
2021-06-02 09:11:10.043 [main] INFO  w.e.spring.ioc.spel.method.MethodInvocationSample-==> expreString: #parseInt('2') == #parseInt2('3'), result: false
2021-06-02 09:11:10.046 [main] INFO  w.e.spring.ioc.spel.method.MethodInvocationSample-==> expreString: #parseInt('2') == #parseInt2('2'), result: true

6. Elvis运算符

是三元运算符的特殊写法,避免null报错的情况

name ?: "other" 

等价于:name != null ? name : "other"
7. 安全保证
list?.length 
等价于: list == null ? null : list.length
8. 直接使用java代码new/instance of
9. 其他

集合定义访问

package win.elegentjs.spring.ioc.spel.collection;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

import java.util.List;
import java.util.Map;

@Data
public class Person {

    @Value("#{{1,2,3}}")
    private List<Integer> cards;

    @Value("#{{1,2,3}[0]}")
    private Integer first;

    @Value("#{{'name': 'zhangsan', 'age': 20}}")
    private Map<String, Object> dict;

    @Value("#{{'name': 'zhangsan', 'age': 20}['name']}")
    private String dictName;

}

执行结果:

package win.elegentjs.spring.ioc.spel.collection;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Slf4j
public class PersonSample {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);

        Person p = context.getBean(Person.class);

        log.info("==> p: {}", p);
    }

}

// result:
2021-06-02 09:38:41.933 [main] INFO  w.e.spring.ioc.spel.collection.PersonSample-==> p: Person(cards=[1, 2, 3], first=1, dict={name=zhangsan, age=20}, dictName=zhangsan)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值