Java进击框架:Spring-表达式(三)

前言

本章节主要介绍,Spring的表达式。

表达式语言(SpEL)

Spring表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但是提供了额外的特性,最显著的是方法调用和基本的字符串模板功能。

虽然SpELSpring组合中表达式求值的基础,但它并不直接绑定到Spring,可以独立使用。SpEL,就好像它是一种独立的表达式语言。这需要创建一些引导基础设施类,比如解析器。大多数Spring用户不需要处理这个基础设施,相反,他们可以只编写表达式字符串来进行评估。

表达式语言支持以下功能:

  • 文字表达式
  • 布尔和关系运算符
  • 正则表达式
  • 类别表达式
  • 访问属性、数组、列表和映射
  • 方法调用
  • 关系运算符
  • 分配
  • 调用构造函数
  • Bean引用
  • 阵列构造
  • 内嵌列表
  • 内嵌地图
  • 三元运算符
  • 变量
  • 用户定义的函数
  • 集合投影
  • 集合选择
  • 模板表达式

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'hello world'");
        String value = exp.getValue(String.class);
        System.out.println(value);
        /** Output:
         *  hello world
         */
    }
}

您最有可能使用的SpEL类和接口位于org.springframework.expression包及其子包,例如spel.support.

ExpressionParser接口负责解析表达式字符串。在前面的示例中,表达式string是用单引号括起来的字符串文字。这Expression接口负责计算之前定义的表达式字符串。可以抛出两个异常,ParseExceptionEvaluationException

SpEL支持广泛的特性,比如调用方法、访问属性和调用构造函数。比如:调用toUpperCase()转换为大写,示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'hello world'.toUpperCase()");
        String value = exp.getValue(String.class);
        System.out.println(value);
        /** Output:
         *  HELLO WORLD
         */
    }
}

字符串有很多的方法,比如:length()方法、concat()方法、getBytes()方法等等,这里不一一列举,有兴趣的自行了解。

如果当一个对象不能通过get()方法获取到值时,你第一时间是不是想到用反射的方式去获取?SpEL表达式也可以帮你解决这个问题。

示例代码如下:

public class A{
    private String name;

    public A(String name) { this.name = name; }

    public String getName() { return name; }

    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        A a = new A("张三");
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("name");
        String name = exp.getValue(a,String.class);
        System.out.println(name);
        /** Output:
         *  张三
         */
    }
}

在计算表达式以解析属性、方法或字段并帮助执行类型转换时,使用EvaluationContext接口。Spring提供了两种实现。

  • SimpleEvaluationContext:公开基本SpEL语言特性和配置选项的子集,用于不需要SpEL语言语法的全部范围并且应该有意义地加以限制的表达式类别。示例包括但不限于数据绑定表达式和基于属性的过滤器。
  • StandardEvaluationContext:公开完整的SpEL语言特性和配置选项集。您可以使用它来指定默认根对象并配置每个可用的与评估相关的策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的子集。它不包括Java类型引用、构造函数和bean引用。它还要求您显式地选择表达式中对属性和方法的支持级别。

  • 类型变换

默认情况下,SpEL使用Spring coreConversionService可用的转换服务。此转换服务带有许多用于常见转换的内置转换器,但它也是完全可扩展的,因此您可以在类型之间添加自定义转换。此外,它是泛型感知的。这意味着,当您在表达式中使用泛型类型时,SpEL会尝试转换以维护遇到的任何对象的类型正确性。

public class A{
    public List<Boolean> list = new ArrayList<>();
}
public class Test {
    public static void main(String[] args) {
        A a = new A();
        a.list.add(true);
        EvaluationContext evaluationContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        ExpressionParser parser = new SpelExpressionParser();
        parser.parseExpression("list[0]").setValue(evaluationContext,a,"false");
        System.out.println(JSONObject.toJSONString(a.list));
        /** Output:
         *  [false]
         */
    }
}
  • 解析器配置

可以通过使用解析器配置对象(SpelParserConfiguration)。配置对象控制一些表达式组件的行为。

例如,如果对数组或集合进行索引,并且指定索引处的元素为nullSpEL可以自动创建元素。如果指定的索引超出了数组或列表的当前大小,SpEL可以自动增大数组或列表以容纳该索引。为了在指定的索引处添加元素,SpEL将在设置指定的值之前尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认的构造函数,null将被添加到数组或列表中。如果没有知道如何设置该值的内置或自定义转换器,null将保留在数组或列表中的指定索引处。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        A a = new A();
        a.list.add("hello");
        //参数1:自动空引用初始化,参数2:自动收集
        SpelParserConfiguration configuration = new SpelParserConfiguration(true,true);
        ExpressionParser parser = new SpelExpressionParser(configuration);
        Expression exp = parser.parseExpression("list[2]");
        exp.setValue(a,"world");
        System.out.println(JSONObject.toJSONString(a.list));
        /** Output:
         *  ["hello","","world"]
         */
    }
}

SpEL编译

Spring Framework 4.1包括一个基本的表达式编译器。表达式通常是解释型的,这在计算过程中提供了很大的动态灵活性,但不能提供最佳性能。对于偶尔使用表达式的情况,这很好,但是,当被其他组件(如Spring Integration)使用时,性能可能非常重要,并且不需要动态性。

SpEL编译器旨在满足这一需求。在求值期间,编译器生成一个Java类,该类体现了运行时的表达式行为,并使用该类来实现更快的表达式求值。由于表达式周围缺少类型,编译器在执行编译时使用在表达式的解释求值期间收集的信息。例如,它不知道纯粹来自表达式的属性引用的类型,但是在第一次解释的评估期间,它发现它是什么。当然,如果各种表达式元素的类型随着时间的推移而改变,基于这种派生信息进行编译可能会带来麻烦。由于这个原因,编译最适合于类型信息在重复计算时不会改变的表达式。

考虑下面的基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1

因为前面的表达式涉及数组访问、一些属性解引用和数值运算,所以性能提升非常明显。在一个50000次迭代的示例微基准测试运行中,使用解释器进行评估需要75毫秒,而使用表达式的编译版本只需要3毫秒。

编译器可以在三种模式之一下运行,这三种模式在org.springframework.expression.spel.SpelCompilerMode枚举。这些模式如下:

  • OFF(默认值):编译器关闭。
  • IMMEDIATE:在即时模式下,表达式会尽快编译。这通常发生在第一次解释评估之后。如果编译的表达式失败(通常是由于类型改变,如前所述),表达式求值的调用者会收到一个异常。
  • MIXED:在混合模式下,表达式会随着时间的推移在解释模式和编译模式之间自动切换。经过一定次数的解释运行后,它们会切换到编译形式,如果编译形式出现问题(比如类型改变,如前所述),表达式会自动再次切换回解释形式。稍后,它可能会生成另一个已编译的表单并切换到它。基本上,用户得到的异常IMMEDIATE模式是在内部处理的。

IMMEDIATE模式存在是因为MIXED模式可能会导致具有副作用的表达式出现问题。如果编译后的表达式在部分成功后爆炸,它可能已经做了一些影响系统状态的事情。如果发生了这种情况,调用者可能不希望它以解释模式静默地重新运行,因为表达式的一部分可能运行了两次。

public class Test {
    public static void main(String[] args) {
        SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,new Test().getClass().getClassLoader());
        ExpressionParser parser = new SpelExpressionParser(configuration);
        Expression exp = parser.parseExpression("list");
        A a = new A();
        Object value = exp.getValue(a);
        System.out.println(value);
        /** Output:
         *  []
         */
    }
}

当指定编译器模式时,也可以指定类加载器(允许传递null)。编译表达式是在提供的任何下创建的子类加载器中定义的。重要的是要确保,如果指定了类加载器,它可以看到表达式求值过程中涉及的所有类型。如果不指定类加载器,则使用默认的类加载器(通常是在表达式求值期间运行的线程的上下文类加载器)。

配置编译器的第二种方法是在SpEL嵌入到其他组件中,并且不可能通过配置对象对其进行配置时使用。

Spring Framework 4.1开始,基本的编译框架已经就绪。然而,该框架还不支持编译每一种表达式。最初的焦点是可能在性能关键的上下文中使用的通用表达式。目前无法编译以下几种表达式:

  • 涉及赋值的表达式
  • 依赖转换服务的表达式
  • 使用自定义解析器或访问器的表达式
  • 使用选择或投影的表达式

将来会有更多类型的表达式可以编译。

Bean定义中的表达式

您可以使用SpEL表达式和基于XML或基于注释的配置元数据来定义BeanDefinition实例。在这两种情况下,定义表达式的语法都是以下形式#{ <expression string> }

  • XML配置

使用表达式设置属性或构造函数参数值,如下例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans">
    <bean id="a" class="com.example.A">
        <property name="random" value="#{T(java.lang.Math).random() * 100.0}"></property>
    </bean>
</beans>
public class A{
    private Integer random;

    public Integer getRandom() { return random; }

    public void setRandom(Integer random) { this.random = random; }
}
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        A bean = applicationContext.getBean(A.class);
        System.out.println(bean.getRandom());
        /** Output:
         *  15
         */
    }
}

应用程序上下文中的所有bean都可以作为预定义变量使用,并具有它们的公共bean名称。这包括标准的上下文beans,例如environment(类型org.springframework.core.env.Environment)以及systemPropertiessystemEnvironment(类型Map<String, Object>)来访问运行时环境。

你想知道系统环境有哪些变量可以通过System.getProperties()方法和System.getenv()方法获取systemPropertiessystemEnvironment,示例代码如下:

public class Test {
    public static void main(String[] args) {
        Map<String, String> getenv = System.getenv();
        System.out.println(JSONObject.toJSONString(getenv));
        Properties properties = System.getProperties();
        System.out.println(JSONObject.toJSONString(properties));
    }
}

下面的示例显示了对systemProperties作为SpEL变量的bean

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="a" class="com.example.A">
        <property name="name" value="#{systemProperties['java.class.version']}"></property>
    </bean>
</beans>
public class A{
    private String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        A bean = applicationContext.getBean(A.class);
        System.out.println(bean.getName());
        /** Output:
         *  52.0
         */
    }
}

还可以通过名称引用其他bean属性,如下例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="a" class="com.example.A">
        <property name="name" value="张三"></property>
    </bean>
    <bean id="test" class="com.example.Test">
        <property name="name" value="#{a.name}"></property>
    </bean>
</beans>
public class Test {
    private String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Test bean = applicationContext.getBean(Test.class);
        System.out.println(bean.getName());
        /** Output:
         *  张三
         */
    }
}
  • 注释配置

若要指定默认值,可以将@Value字段、方法和方法或构造函数参数的注释。

@Component
public class Test {
    @Value("#{systemProperties['java.class.version']}")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example");
        Test bean = applicationContext.getBean(Test.class);
        System.out.println(bean.getName());
        /** Output:
         *  52.0
         */
    }
}

语言参考

下面主要介绍他们的一些使用。

  • 文字表达式

SpEL支持以下类型的文字表达式。

(1)String

前面内容有介绍过,不仅可以打印字符串,还能调用字符串的方法。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        String value = parser.parseExpression("'hello world'").getValue(String.class);
        System.out.println(value);
        String value2 = parser.parseExpression("'hello world'.length()").getValue(String.class);
        System.out.println(value2);
        /** Output:
         *  hello world
         *  11
         */
    }
}

(2)数值:整数(int或者long),十六进制(int或者long),真实(float或者double)

数字支持使用负号、指数符号和小数点。默认情况下,实数通过使用Double.parseDouble()

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Double value = parser.parseExpression("3.14").getValue(Double.class);
        System.out.println(value);
        Integer value2 = parser.parseExpression("0x7FFFFFFF").getValue(Integer.class);
        System.out.println(value2);
        /** Output:
         *  3.14
         *  2147483647
         */
    }
}

(3)布尔值:true或者false

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Boolean value = parser.parseExpression("true").getValue(Boolean.class);
        System.out.println(value);
        /** Output:
         *  true
         */
    }
}

(4)

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Boolean value = parser.parseExpression("null").getValue(Boolean.class);
        System.out.println(value);
        /** Output:
         *  null
         */
    }
}
  • 属性、数组、列表、映射和索引器

获取属性,示例代码如下:

public class A{
    private String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    public static void main(String[] args) {
        A a = new A();
        a.setName("张三");
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        String value = parser.parseExpression("name").getValue(context,a,String.class);
        System.out.println(value);
        /** Output:
         *  张三
         */
    }
}

数组和列表的内容是通过使用方括号符号获得的,如下例所示:

public class Test {
    public static void main(String[] args) {
        List<A> list = new ArrayList<>();
        A a = new A();
        a.setName("张三");
        list.add(a);
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("[0].name").getValue(context,list,String.class);
        System.out.println(value);
        /** Output:
         *  张三
         */
    }
}

map的内容通过在括号内指定字面键值来获得,示例代码如下:

public class Test {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("name","张三");
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("[name]").getValue(context,map,String.class);
        //parser.parseExpression("[name]").setValue(context,map,"李四");赋值
        System.out.println(value);
        /** Output:
         *  张三
         */
    }
}
  • 内嵌List

您可以使用直接在表达式中表示列表{}符号。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        List value = (List) parser.parseExpression("{'a','b','c','d'}").getValue(context);
        System.out.println(value);
        List value2 = (List) parser.parseExpression("{{'a'},{'b'}}").getValue(context);
        System.out.println(value2);
        /** Output:
         *  [a, b, c, d]
         *  [[a], [b]]
         */
    }
}

{}本身意味着一个空列表。出于性能原因,如果列表本身完全由固定文字组成,则创建一个常量列表来表示表达式(而不是在每次求值时构建一个新列表)。

  • 内嵌Map

您也可以使用以下命令在表达式中直接表示地图{key:value}符号。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Map value = (Map) parser.parseExpression("{'name':'qwe'}").getValue(context);
        System.out.println(value);
        Map value2 = (Map) parser.parseExpression("{'key':{'name':'qwe'},'key2':{'name':'qwe2'}}").getValue(context);
        System.out.println(value2);
        /** Output:
         *  {name=qwe}
         *  {key={name=qwe}, key2={name=qwe2}}
         */
    }
}

{:}本身就意味着一张空地图。出于性能原因,如果映射本身由固定文字或其他嵌套的常量结构(列表或映射)组成,则创建一个常量映射来表示表达式(而不是在每次求值时构建一个新的映射)。

  • 构建数组

您可以使用熟悉的Java语法来构建数组,可以选择提供一个初始化器来在构造时填充数组。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        int[] value = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
        System.out.println(value);
    }
}

构造多维数组时,当前不能提供初始值设定项。

  • 方法调用

您可以使用典型的Java编程语法来调用方法。您也可以在文字上调用方法。也支持可变参数。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        String value = parser.parseExpression("'hello'.toUpperCase()").getValue(String.class);
        System.out.println(value);
        StandardEvaluationContext context = new StandardEvaluationContext(Test.class);
        String value2 = parser.parseExpression("print('hello')").getValue(context,String.class);
        System.out.println(value2);
        /** Output:
         *  HELLO
         *  hello world
         */
    }
    public static String print(String param){
        return param+" world";
    }
}
  • 关系运算符

使用标准运算符表示法支持关系运算符(等于、不等于、小于、小于或等于、大于和大于或等于)。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Boolean value = parser.parseExpression("1==1").getValue(Boolean.class);
        System.out.println(value);
        /** Output:
         *  true
         */
    }
}

大于和小于的比较null遵循一个简单的规则:null被视为无(即不为零)。因此,任何其他值总是大于null (X > null总是true)并且没有其他值小于零(X < null总是false).

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Boolean value = parser.parseExpression("1>null").getValue(Boolean.class);
        System.out.println(value);
        Boolean value2 = parser.parseExpression("1<null").getValue(Boolean.class);
        System.out.println(value2);
        /** Output:
         *  true
         *  false
         */
    }
}

如果您更喜欢数字比较,请避免基于数字的比较null有利于与零进行比较的比较(例如,X > 0或者X < 0)。

除了标准的关系运算符,SpEL还支持instanceof和基于正则表达式matches操作。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        boolean value = parser.parseExpression(
                "'xyz' instanceof T(Integer)").getValue(Boolean.class);
        System.out.println(value);
        boolean value2 = parser.parseExpression(
                "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
        System.out.println(value2);
        /** Output:
         *  false
         *  true
         */
    }
}

使用基本类型时要小心,因为它们会立即被打包成它们的包装类型。举个例子,1 instanceof T(int)评估为false,而1 instanceof T(Integer)评估为true,不出所料。

每个符号操作符也可以被指定为纯字母的等价物。这避免了所使用的符号对于嵌入表达式的文档类型具有特殊含义的问题(例如在XML文档中)。对应的文本是:lt (<)gt (>)le (<=)ge (>=)eq (==)ne (!=)div (/)mod (%)not (!)

  • 逻辑运算符

SpEL支持以下逻辑运算符:and (&&)or (||)not (!)

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
//        boolean value = parser.parseExpression("true && false").getValue(Boolean.class);
        boolean value = parser.parseExpression("true and false").getValue(Boolean.class);
        System.out.println(value);
//        boolean value2 = parser.parseExpression("not true").getValue(Boolean.class);
        boolean value2 = parser.parseExpression("!true").getValue(Boolean.class);
        System.out.println(value2);
        /** Output:
         *  false
         *  false
         */
    }
}
  • 数学运算符

您可以使用加法运算符(+)在数字和字符串上。你可以用减法(-)、乘法(*),以及除法(/)运算符只对数字进行运算。您也可以使用模数(%)和指数幂(^)关于数字的运算符。强制执行标准运算符优先级。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Integer value = parser.parseExpression("1+2*7").getValue(Integer.class);
        System.out.println(value);
        Integer value2 = parser.parseExpression("1/2+2-3").getValue(Integer.class);
        System.out.println(value2);
        /** Output:
         *  15
         *  -1
         */
    }
}
  • 赋值运算符

若要设置属性,请使用赋值运算符(=)这通常是在调用setValue但是也可以在对getValue。示例代码如下:

public class Test {
    public static void main(String[] args) {
        A a = new A();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
        parser.parseExpression("name").setValue(context,a,"张三");
        System.out.println(a.getName());
        String value2 = parser.parseExpression("name='李四'").getValue(context,a,String.class);
        System.out.println(a.getName());
        /** Output:
         *  张三
         *  李四
         */
    }
}
  • 构造器

您可以通过使用new操作,调用构造函数的运算符,示例代码如下:

public class A{
    private String name;

    public A(String name) { this.name = name; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext(A.class);
        Object value = parser.parseExpression("new com.example.A('张三')").getValue(context);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  {"name":"张三"}
         */
    }
}
  • 变量

您可以通过使用#variableName语法。变量通过使用setVariable方法打开EvaluationContext实现。

public class A{
    private String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    public static void main(String[] args) {
        A a = new A();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
        context.setVariable("name","李四");
        Object value = parser.parseExpression("name = #name").getValue(context,a);
        System.out.println(JSONObject.toJSONString(a));
        /** Output:
         *  {"name":"李四"}
         */
    }
}
  • #this和#root变量

#this变量始终被定义并引用当前的评估对象(根据该评估对象解析非限定引用),#root总是指根。

示例代码如下:

public class Test {
    public static void main(String[] args) {
        List<Integer> primes = new ArrayList<Integer>();
        primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        context.setVariable("primes",primes);
        //列表中所有大于10的质数(使用选择?{...})
        List<Integer> value = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  [11,13,17]
         */
    }
}
  • 功能

您可以通过注册可以在表达式字符串中调用的用户定义函数来扩展SpEL。该函数通过EvaluationContext。示例代码如下:

public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        context.setVariable("reverseString",Test.class.getDeclaredMethod("reverseString",String.class));
        String value = parser.parseExpression("#reverseString('hello world')").getValue(context, String.class);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  "dlrow olleh"
         */
    }
    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
  • Bean引用

你可以使用表达式访问bean,示例代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="a" class="com.example.A">
        <property name="name" value="张三"></property>
    </bean>
</beans>
public class MyBeanResolver implements BeanResolver {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Override
    public Object resolve(EvaluationContext context, String beanName) throws AccessException {
        return applicationContext.getBean(beanName);
    }
}
public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new MyBeanResolver());
        Object value = parser.parseExpression("@a").getValue(context);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  {"name":"张三"}
         */
    }
}

要访问工厂bean本身,您应该在bean名称前加上前缀&符号。

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new MyBeanResolver());
        Object value = parser.parseExpression("&fb").getValue(context);
        System.out.println(JSONObject.toJSONString(value));=
    }
}
  • 三元运算符(If-Then-Else)

您可以使用三元运算符在表达式中执行if-then-else条件逻辑。示例代码如下:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        String value = parser.parseExpression("true?'yes':'no'").getValue(String.class);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  "yes"
         */
    }
}
  • Elvis操作

Elvis运算符是三元运算符语法的缩写,用于绝妙的语言。使用三元运算符语法时,通常需要将一个变量重复两次,示例代码如下:

public class Test {
    public static void main(String[] args) {
        A a = new A();
        ExpressionParser parser = new SpelExpressionParser();
        String value = parser.parseExpression("name?:'空值'").getValue(a,String.class);
        System.out.println(JSONObject.toJSONString(value));
        a.setName("张三");
        String value2 = parser.parseExpression("name?:'空值'").getValue(a,String.class);
        System.out.println(JSONObject.toJSONString(value2));
        /** Output:
         *  "空值"
         *  "张三"
         */
    }
}

您可以使用Elvis运算符在表达式中应用默认值。下列范例显示如何在中使用Elvis运算子@Value注解

@Component
public class Test {
    @Value("#{systemProperties['java.test']?: 0}")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example");
        Test bean = applicationContext.getBean(Test.class);
        System.out.println(bean.getName());
        /** Output:
         *  0
         */
    }
}
  • 安全导航操作

安全导航运算符用于避免NullPointerException并且来自于绝妙的语言。通常,当您引用一个对象时,您可能需要在访问该对象的方法或属性之前验证它不为null。为了避免这种情况,安全导航运算符返回null,而不是引发异常。

示例代码如下:

public class Test {
    private A a;

    public A getA() { return a; }

    public void setA(A a) { this.a = a; }

    public static void main(String[] args) {
        Test test = new Test();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        String value = parser.parseExpression("a?.name").getValue(context,test,String.class);
        System.out.println(JSONObject.toJSONString(value));
        String value2 = parser.parseExpression("a.name").getValue(context,test,String.class);
        System.out.println(JSONObject.toJSONString(value2));
        /** Output:
         *  null
         *  SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
         */
    }
}
  • 集合选择

Selection是一个强大的表达式语言特性,它允许您通过从条目中进行选择,将一个源集合转换为另一个集合。

选择使用的语法为.?[selectionExpression]。它筛选集合并返回包含原始元素子集的新集合。示例代码如下:

public class A{
    private Integer id;
    private String name;

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    private List<A> a;

    public List<A> getA() {
        return a;
    }

    public void setA(List<A> a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Test test = initValue();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("a.?[name=='张三']").getValue(context,test);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  [{"id":1,"name":"张三"},{"id":3,"name":"张三"}]
         */
    }
    public static Test initValue(){
        Test test = new Test();
        List<A> a = new ArrayList<>();
        A a1 = new A();
        a1.setId(1);
        a1.setName("张三");
        a.add(a1);
        A a2 = new A();
        a2.setId(2);
        a2.setName("李四");
        a.add(a2);
        A a3 = new A();
        a3.setId(3);
        a3.setName("张三");
        a.add(a3);
        test.setA(a);
        return test;
    }
}

数组和任何实现的对象都支持选择java.lang.Iterable或者java.util.Map。对于列表或数组,选择标准是针对每个单独的元素进行评估的。针对Map,针对每个Map条目评估选择标准(Java类型的对象Map.Entry)。每个映射条目都有自己的keyvalue可作为在选择中使用的属性进行访问。

public class Test {
    private Map<String, Integer> map;

    public Map<String, Integer> getMap() { return map; }

    public void setMap(Map<String, Integer> map) { this.map = map; }

    public static void main(String[] args) {
        Test test = initValue();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("map.?[value%2==0]").getValue(context, test);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  {"k3":16,"k":10}
         */
    }

    public static Test initValue() {
        Test test = new Test();
        Map<String, Integer> map = new HashMap<>();
        map.put("k", 10);
        map.put("k1", 13);
        map.put("k2", 15);
        map.put("k3", 16);
        map.put("k4", 17);
        test.setMap(map);
        return test;
    }
}

除了返回所有选定的元素之外,您还可以只检索第一个或最后一个元素。要获得匹配选择的第一个元素,语法是.^[selectionExpression]。要获得最后一个匹配的选择,语法是.$[selectionExpression]

public class Test {
    public static void main(String[] args) {
        Test test = initValue();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("map.^[value%2==0]").getValue(context, test);
        System.out.println(JSONObject.toJSONString(value));
        Object value2 = parser.parseExpression("map.$[value%2==0]").getValue(context, test);
        System.out.println(JSONObject.toJSONString(value2));
        /** Output:
         *  {"k3":16}
         * {"k":10}
         */
    }
}
  • 集合投影

投影让集合驱动子表达式的计算,结果是一个新的集合。投影的语法是.![projectionExpression]

示例代码如下:

public class A{
    private Integer id;
    private String name;

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }
}
public class Test {
    private List<A> a;

    public List<A> getA() {
        return a;
    }

    public void setA(List<A> a) {
        this.a = a;
    }
    public static void main(String[] args) {
        Test test = initValue();
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Object value = parser.parseExpression("a.![name]").getValue(context, test);
        System.out.println(JSONObject.toJSONString(value));
        /** Output:
         *  ["张三","李四","张三"]
         */
    }

    public static Test initValue(){
        Test test = new Test();
        List<A> a = new ArrayList<>();
        A a1 = new A();
        a1.setId(1);
        a1.setName("张三");
        a.add(a1);
        A a2 = new A();
        a2.setId(2);
        a2.setName("李四");
        a.add(a2);
        A a3 = new A();
        a3.setId(3);
        a3.setName("张三");
        a.add(a3);
        test.setA(a);
        return test;
    }
}

数组和任何实现java.lang.Iterablejava.util.Map的东西都支持投影。当使用Map驱动投影时,投影表达式针对Map中的每个条目(表示为Java Map.Entry)进行计算。跨Map的投影的结果是一个列表,该列表由投影表达式对每个映射条目的求值组成。

  • 表达式模板

表达式模板允许将文字文本与一个或多个评估块混合。每个评估块由您可以定义的前缀和后缀字符分隔。常见的选择是使用#{ }作为分隔符,如下例所示:

public class Test {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Object value = parser
                .parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext())
                .getValue();
        System.out.println(value);
        /** Output:
         *  "random number is 0.8064802857193315"
         */
    }
}

字符串的计算方法是将字面文本’random number is '与#{}分隔符内表达式的计算结果连接起来(在本例中,是调用random()方法的结果)。parseExpression()方法的第二个参数是ParserContext类型。ParserContext接口用于影响表达式的解析方式,以支持表达式模板功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值