SpringFramework核心技术四:Spring表达式语言知识点(SpEL)

  1. 云栖社区>
  2. 博客>
  3. 正文

SpringFramework核心技术四:Spring表达式语言知识点(SpEL)

hello熊本 2018-06-27 11:44:03 浏览1104
展开阅读全文

Spring表达式

Sprng表达式,可以适用于几乎所有的Spring产品中,是一种非常重要的表达式语言,下面我们一起来看看。

一、介绍

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

虽然还有其他几种Java表达式语言可用 - OGNL,MVEL和JBoss EL,仅举几例 - 创建Spring表达式语言是为了向Spring社区提供单一支持良好的表达式语言,可用于所有产品中组合。它的语言特性受到Spring产品组合中项目需求的驱动,包括基于Eclipse的Spring Tool Suite中代码完成支持的工具需求。也就是说,SpEL基于技术不可知的API,允许在需要时集成其他表达式语言实现。

虽然SpEL是Spring产品组合中表达式评估的基础,但它并不直接与Spring结合,可以独立使用。为了自成一体,本章中的许多示例都使用SpEL,就好像它是独立的表达式语言一样。这需要创建一些引导基础结构类,比如解析器。大多数Spring用户不需要处理这个基础设施,只需要创建表达式字符串进行评估。这种典型用法的一个例子是将SpEL集成到创建XML或基于注释的bean定义中,如定义bean定义的表达式支持部分所示。

本章介绍表达式语言,其API以及其语言语法的特性。在几个地方,一个Inventor和Inventor的Society类被用作表达式评估的目标对象。这些类声明和用于填充它们的数据在本章最后列出。

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

  • 文字表达
  • 布尔和关系运算符
  • 常用表达
  • 类表达式
  • 访问属性,数组,列表,地图
  • 方法调用
  • 关系运算符
  • 分配
  • 调用构造函数
  • Bean引用
  • 阵列构建
  • 内联列表
  • 内联地图
  • 三元运营商
  • 变量
  • 用户定义的功能
  • 集合投影
  • 集合选择
  • 模板化表达式

二、Evaluation

以下代码引入了SpEL API来评估文字字符串表达式“Hello World”。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

消息变量的值只是’Hello World’。

您最有可能使用的SpEL类和接口位于程序包 org.springframework.expression及其子程序包(例如)中spel.support。

该接口ExpressionParser负责解析表达式字符串。在这个例子中,表达式字符串是由周围的单引号表示的字符串文字。该接口Expression负责评估以前定义的表达式字符串。有两个例外,可以被抛出,ParseException和EvaluationException在调用parser.parseExpression和exp.getValue 分别被抛出。

SpEL支持广泛的功能,例如调用方法,访问属性和调用构造函数。
作为方法调用的一个例子,我们concat在字符串文字上调用方法。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();

现在输出的是:’Hello World!’。

作为调用JavaBean属性的示例,可以调用String属性Bytes,如下所示。

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();

SpEL还支持使用标准点符号的嵌套属性,即 prop1.prop2.prop3属性值的设置
公共字段也可以被访问。

ExpressionParser parser = new SpelExpressionParser();

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

可以调用String的构造函数,而不是使用字符串文字。

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

请注意使用通用方法public T getValue(Class desiredResultType)。使用此方法不需要将表达式的值转换为所需的结果类型。EvaluationException如果该值不能T转换为类型或使用注册类型转换器进行转换,则会抛出An 。

SpEL更常见的用法是提供一个针对特定对象实例(称为根对象)进行评估的表达式字符串。该示例显示如何name从Inventor类的实例中检索属性或创建布尔条件:

// 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");
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true

三、EvaluationContext

EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开Spal语言特性和配置选项的子集。示例包括但不限于数据绑定表达式,基于属性的过滤器等。

  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。它还要求明确选择对表达式中属性和方法的支持级别。默认情况下,create()静态工厂方法只启用对属性的读取访问。您还可以获取构建器以配置所需的确切支持级别,并将目标作为以下一项或几项的组合:

  • PropertyAccessor仅自定义(不反射)
  • 用于只读访问的数据绑定属性
  • 用于读取和写入的数据绑定属性

类型转换

默认情况下,SpEL使用Spring core(org.springframework.core.convert.ConversionService)中提供的转换服务。这种转换服务附带了许多内置的转换器,可以进行常规转换,但也可以完全扩展,因此可以添加类型之间的自定义转换。此外,它具有泛型意识的关键能力。这意味着,当在表达式中使用泛型类型时,SpEL将尝试转换以维护其遇到的任何对象的类型正确性。

这在实践中意味着什么?假设setValue()正在使用赋值来设置List属性。该属性的类型实际上是List<Boolean>。SpEL将认识到列表中的元素Boolean在被放置之前需要被转换。一个简单的例子:

class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext().forReadOnlyDataBinding().build();

// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b will be false
Boolean b = simple.booleanList.get(0);

四、解析器配置

可以使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)配置SpEL表达式解析器。配置对象控制一些表达式组件的行为。例如,如果索引到数组或集合以及指定索引处的元素,null 则可以自动创建该元素。当使用由一系列属性引用组成的表达式时,这非常有用。如果索引到数组或列表中并指定超出数组或列表当前大小末尾的索引,则可以自动增大数组或列表以适应该索引。

class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

五、SpEL编译

Spring Framework 4.1包含一个基本的表达式编译器。表达式通常被解释为在评估过程中提供了很大的动态灵活性,但不能提供最佳性能。对于偶然的表达用法,这很好,但是当像Spring Integration这样的其他组件使用时,性能可能非常重要,并且不需要动态性。

SpEL编译器旨在解决此需求。编译器将在评估过程中动态生成一个真正的Java类,以体现表达式行为并使用它来实现更快的表达式评估。由于缺少对表达式的打字,编译器在执行编译时使用在表达式的解释评估期间收集的信息。例如,它不完全知道表达式的属性引用的类型,但是在第一次解释评估期间,它会查明它是什么。当然,如果各种表达式元素的类型随着时间的推移而变化,那么基于这些信息的编译可能会在稍后造成麻烦。出于这个原因,编译最适合于在重复评估时其类型信息不会改变的表达式。

对于像这样的基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1
其中涉及数组访问,某些属性取消引用和数字操作,性能增益可能非常明显。在50000次迭代的示例微基准测试中,仅使用解释器评估75ms,使用表达式的编译版本仅评估3ms。

编译器配置

编译器默认情况下未打开,但有两种方法可以打开它。可以使用前面讨论过的解析器配置过程打开它,或者当SpEL用法嵌入到另一个组件内时使用系统属性打开它。本节讨论这两个选项。

了解编译器可以在枚举(org.springframework.expression.spel.SpelCompilerMode)中捕获的几种模式很重要。模式如下:

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

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

SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
    this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);

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

配置编译器的第二种方式是在SpEL嵌入到其他组件中时使用,并且可能无法通过配置对象进行配置。在这些情况下,可以使用系统属性。该属性 spring.expression.compiler.mode可被设置为一个SpelCompilerMode 枚举值(off,immediate,或mixed)。

编译器限制

自Spring Framework 4.1以来,基本的编译框架已经到位。但是,该框架还不支持编译各种表达式。最初的重点是可能用于性能关键环境的常用表达式。以下几种表达方式目前无法编译:

  • 涉及分配的表达
  • 表达依赖于转换服务
  • 使用自定义解析器或访问器的表达式
  • 使用选择或投影的表达式
  • 越来越多类型的表达将在未来进行编辑。

六、表达式在bean定义中

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

1.XML配置

可以使用如下所示的表达式来设置属性或构造函数参数值。

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

变量systemProperties是预定义的,所以你可以在你的表达式中使用它,如下所示。请注意,您不必# 在此上下文中将符号放在预定义变量前面。

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

例如,您也可以通过名称引用其他bean属性。

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>

2.基于注解的使用(推荐的使用方式)

该@Value注释可被放置在字段,方法和方法/构造函数的参数来指定一个缺省值。
以下是设置字段变量的默认值的示例。

public static class FieldValueTestBean

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

下面显示了等价但属性setter方法。

public static class PropertyValueTestBean

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

自动装配的方法和构造函数也可以使用@Value注释。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

好啦,上面使一些Spring表达式的一些基本的知识点。下一节看看如何使用!

网友评论

登录后评论
0/500
评论
hello熊本
+ 关注