Spring Expression Language(SpEL)

Spring Expression Language(SpEL)

spring的一种表达式。用来动态的获取,值、对象等。
表达式语言给静态Java语言增加了动态功能
SpEL 简单的使用是通过#{}来获取等一些操作。但是不能浅显的认为#{}就是SpEL,后续我们会讲到#{}的设定。
说说常用的两种方式去设置SpEL

Annotation

注解。@Value()方便快捷。
注解
你可以在里面方式任何符合SpEL规范的语句,并把它的返回值注入到对应的属性中。

XML

xml配置,老生常谈,经典配置。
XML


规则

你会发行,形式是一样的。重要的是规则。来谈一下规则。

运算符

运算符类型 运算符 例子
算术运算 +、-、*、/、%、^ 1+1、2^3、
比较运算 <、>、==、<=、>= 、lt、 gt、 eq 、le 、ge 1 lt 2
条件运算 ?:(ternary) 、?:(Elvis) 三元运算符 A?true : false
逻辑运算 and、or、not、| A and B
正则表达式 matches #{email matches ‘[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com’}
类型判断 instanceof ‘xyz’ instanceof T(int)

这里比较特殊的是mathes 他是作为运算符性质的运算。
Elivis运算符拿出来介绍一下, “表达式1?:表达式2”,从Groovy语言引入用于简化三目运算符的,当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2

ExpressionParser parser = new SpelExpressionParser();

    Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
    tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

    StandardEvaluationContext context = new StandardEvaluationContext(tesla);

    String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
    System.out.println(city); // Smiljan

    tesla.setPlaceOfBirth(null);

    city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);

    System.out.println(city); // null - does not throw NullPointerException!!!

PlaceOfBirth?.City这里,通过?符号,增加PlaceOfBirth判断。如果PlaceOfBirth是null,则不会去获取City,从而避免程序报错。如果PlaceOfBirth是null,会直接返回null

获取方式的问题

比如一些基本类型还有string这样的引用类型的
#{1} #{'abcd'} #{1.0}可以都可直接输入并返回对应的类型。又被SpEL称为字面值。
用的比较少,因为无论是用注解还是用xml都可以直接输入这些值,不需要再包装一层SpEL。
对于集合,比如List可以通过#{List[下标]}来获取对应的Value,数组和Set集合也是一样的。Map可以同过#{Map[key]}来获取对应的值,比如key是string类型的一个歌单,用的是歌曲名称作为key。#{歌单['歌曲名称']}。这里没有关心返回的是什么。

从哪里获取值的问题。

bean

这个就是从其他的bean中获取值了,用这个bean的名字,通过.来获取对应的属性。
比如一个bean,employee,有个属性是名字。
#{employee.name}就可以获取对应的名字。

properties配置文件

可以在properties中定义属性,当然是key=value的形式。
比如在application.properties中自定义的一些属性,通过#{key}获取对应的value。注意的是如果是自定义的配置文件,得让spring获取到它。

xml

可以直接在xml中定义properties,然后注入到对应的bean。

调用方法的问题

调用方法,要么通过对象,进行调用,要么通过类进行调用。

T()

介绍一下这个标记。通过T()可以获取对应的类,()里面放置的是类的类全限定名。比如调用系统当前时间完整的式子:
#{T(System).currentTimeMillis()}
比如获取PI
#{T(java.lang.Math).PI}
住的注意的是,调用方法是需要加上()。实际上这应该很容易记住。

获取对象

#{new java.util.Date()}


高级

自定义类、方法

自定义类,自定义方法。用#{}获取。
看到这里你可能会了解到,类或者对象进行加载的时候,是通过你给的全限定名去项目中获取的。也就是说你可以用SpEL获取你项目编写的任何类调用任何方法。

集合运算符

  • .?[]

它可以对集合进行过滤,得到集合的一个子集。
用歌单为例子
#{歌单集合.?[歌手名称 eq '周杰伦']}
这样就可以获取歌单中歌手为周杰伦的歌,返回的是一个新的集合。
这里用的是阐述性的伪代码。[]中放置的是过滤的条件语句

  • .^[] .$[]
    用来在集合中查询第一个和最后一个匹配项
    #{歌单集合.^[歌手名称 eq '周杰伦']}获取歌单中第一个歌手为周杰伦的歌
    #{歌单集合.$[歌手名称 eq '周杰伦']}获取歌单中最后一个歌手为周杰伦的歌

  • .![]
    它可以遍历集合,将集合中符合特征的子项放置到新的集合中并返回。
    #{歌单集合.![歌名]}获取到所有的歌名的集合
    这的注意的是,这里的[]放置的是集合的特征属性,而不是条件。这样可以将所有的属性值提取出来。


对于这些集合运算符而言,都是集合对象去使用这些操作符,而这些操作符部分返回的又是集合,所以可以链式的使用。比如#{歌曲集合.?[歌手名称 eq '周杰伦'].![歌曲名称]}这样就获取到了所有歌单中周杰伦的歌曲名称的集合。
SpEL非常的灵活,可以通过#{'hello world'[3]}来获取第四个字符。也可以通过{}来直接声明一个内联的列表:

    list:
    {1,2,3,4} 
    {{'a','b'},{'x','y'}}
    map:
    {name:'Nikola',dob:'10 -July-1856'}

代码部分

简单的我们可以构造一个解析器(SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用)。
从helloworld开始

    package cn.javass.spring.chapter5;  
    import junit.framework.Assert;  
    import org.junit.Test;  
    import org.springframework.expression.EvaluationContext;  
    import org.springframework.expression.Expression;  
    import org.springframework.expression.ExpressionParser;  
    import org.springframework.expression.spel.standard.SpelExpressionParser;  
    import org.springframework.expression.spel.support.StandardEvaluationContext;  
    public class SpELTest {  
        @Test  
        public void helloWorld() {  
            ExpressionParser parser = new SpelExpressionParser();  
            Expression expression =  
                parser.parseExpression("('Hello' + ' World').concat(#end)");  
            EvaluationContext context = new StandardEvaluationContext();  
            context.setVariable("end", "!");  
            Assert.assertEquals("Hello World!", expression.getValue(context));  
        }  
    }

  1. 创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
  2. 解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
  3. 构造上下文:准备比如变量定义等等表达式需要的上下文数据。(可选)
  4. 求值:通过Expression接口的getValue方法根据上下文获得表达式值。

    ExpressionParser parser = new SpelExpressionParser();
    // invokes 'getBytes().length'
    Expression exp = parser.parseExpression("'Hello World'.bytes.length");
    int length = (Integer) exp.getValue();

这样可以获取到’Hello World’的长度
使用构造器:

    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = 
        parser.parseExpression("new String('hello world').toUpperCase()");
    String message = exp.getValue(String.class);

看一下这个例子

        // Create and set a calendar
        GregorianCalendar c = new GregorianCalendar();
        c.set(1856, 7, 9);

        // The constructor arguments are name, birthday, and nationality.
        Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("name");

        EvaluationContext context = new StandardEvaluationContext(tesla);
        String name = (String) exp.getValue(context);

在最后一行代码中,变量name会被设置成”Nikola Tesla”,StandardEvaluationContext 类用来指定哪一个对象的name属性会被重新设定。于是,装载到StandardEvaluationContext 类中的Inventor的name属性被替换为”Nikola Tesla”。输出Nikola Tesla。
这里使用一个标准化的上下文。
如下代码:

    // create an array of integers
    List<Integer> primes = new ArrayList<Integer>();
    primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

    // create parser and set variable 'primes' as the array of integers
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("primes",primes);

    // all prime numbers > 10 from the list (using selection ?{...})
    // evaluates to [11, 13, 17]
    List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
            "#primes.?[#this>10]").getValue(context);

#this引用当前的评估对象
这两块都用到了StandardEvaluationContext,实际上就是一个标准上下文计算的容器。我们给StandardEvaluationContext设置primes属性,这样表达式根据上下文,就可以直接的使用这个声明的变量。再上面的例子放入了一个类的实例Inventor,这样当表达式去声明name的时候,就可以直接在这个容器里装载的对象里获取到name属性的值Nikola Tesla。
正常的话应该,new 类的全路径().name来获取。将变量放置在上下文的容器里,表达式就能直接的调用了。这也就是StandardEvaluationContext的作用。这样我们就可以通过将一下自定义的类放置进去,然后直接获取对应的属性或者方法了。
实际上,在给一些@Value操作中,我们也经常会将其他的bean的一些属性注入到当前bean属性中。内部也是通过这样的方法。
针对上面讲述的#{},可以看看:

    String randomPhrase = parser.parseExpression(
            "random number is #{T(java.lang.Math).random()}",
            new TemplateParserContext()).getValue(String.class);

    // evaluates to "random number is 0.7038186818312008"

TemplateParserContext类的内容是:

    public class TemplateParserContext implements ParserContext {

        public String getExpressionPrefix() {
            return "#{";
        }

        public String getExpressionSuffix() {
            return "}";
        }

        public boolean isTemplate() {
            return true;
        }
    }

这里自定义了分隔符delimiters,使用#{最为前缀,}作为后缀。
回头看”random number is #{T(java.lang.Math).random()}”,也就是内部实际上是通过传入定义类TemplateParserContext 来接解析文本”random number is #{T(java.lang.Math).random()}”,当然,默认和更加通用是使用#{}。
更多内容参见Spring官网对于SpringEL的介绍
大佬写的我觉得非常好的博文,SpEL的

猜你喜欢

转载自blog.csdn.net/xl_1851252/article/details/81841907
今日推荐