ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring学习-Spring核心技术(八)

2021-01-08 14:58:27  阅读:218  来源: 互联网

标签:parseExpression String 核心技术 Spring parser getValue 学习 class 表达式


SpEL


读Spring框架官方文档记录。
Spring Expression Language(简称“SpEL”)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。该语言的语法类似于Unified EL,但提供了其他特性,最显著的是方法调用和基本字符串模板功能。
虽然SpEL是Spring组合中表达式计算的基础,但它并不直接与Spring绑定,可以独立使用。为了使其独立,后面许多例子使用SpEL时,就好像它是一种独立的表达语言。这需要创建一些引导基础结构类,比如parser。大多数Spring用户不需要处理这个基础结构,相反,可以只编写表达式字符串进行计算。这种典型用法的一个例子是将SpEL集成到创建XML或基于注解的bean定义中。
后面将介绍SpEL的语言的特性、API和语法。在一些地方,Inventor及Society被用作表达式求值的目标对象。
SpEL支持如下特性:

  • 常量表达式
  • 布尔及关系操作
  • 正则表达式
  • 类表达式
  • 访问属性、数组、lists及maps
  • 方法调用
  • 赋值
  • 调用构造器
  • bean 引用
  • 数组构建
  • 内联lists、maps
  • 三元运算符
  • 变量
  • 用户自定义函数
  • 集合投影
  • 集合选取
  • 模板表达式

1. 求值(Evalutation)

主要介绍SpEL接口的简单使用及表达式语言。最经常使用的SpEL类和接口位于org.springframework.expression包及其子包中,如spell.support。
下面的代码引入了SpEL API来从字符串表达式中获取字符串"Hello World":

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();//message变量值为"Hello World"

ExpressionParser接口负责解析表达式字符串。在前面的示例中,表达式字符串是由周围的单引号表示的字符串文字。Expression接口负责计算前面定义的表达式字符串。调用parser时可能会抛出两个异常,即当调用parser.parseExpression和exp.getValue时,会抛出ParseException和EvaluationException。
下面的例子显示了如使使用方法调用,在字符串字面量上调用concat方法:

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 
String message = (String) exp.getValue();//message变量值为"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并将其转化为大写
String message = exp.getValue(String.class);

注意泛型方法:public<T> T getValue(Class<T>)的使用。使用此方法就不需要将表达式的值强制转换为所需的结果类型。如果不能将值转换为类型T或通过使用已注册的类型转换器进行转换,则会引发EvaluationException。
SpEL更常见的用法是提供一个表达式字符串,该字符串根据特定的对象实例(称为根对象)求值。下面的示例演示如何从Inventor类的实例中检索name属性或创建布尔条件。

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

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

(1) 理解EvaluationContext

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

  • SimpleEvaluationContext:针对不需要完整的SpEL语法并且应该有意义地加以限制的表达式,公开了基本SpEL语言特性和配置选项的子集。包括但不限于数据绑定表达式和基于属性的筛选器。主要排除了Java类型引用,构造器以及bean引用。还要求显式地选择表达式中属性和方法的支持级别。默认情况下,create()静态工厂方法只允许对属性的读访问。还可以获得一个构造器来配置确切的支持级别,目标是下面的一个或某些组合:
    • 自定义PropertyAccessor(无反射)
    • 用于只读访问的数据绑定属性
    • 可读可写的数据绑定属性
  • StandardEvaluationContext:公开完整的SpEL语言特性和配置选项。可以使用它来指定一个默认的根对象,并配置每个可用的与评估相关的策略。

1) 类型转换

默认情况下,SpEL使用Spring中提供的转换服务。这个转换服务提供了许多用于常见转换的内置转换器,但它也是完全可扩展的,因此可以添加类型之间的自定义转换。此外,它是支持泛型的。这意味着,当在表达式中使用泛型类型时,SpEL会尝试转换以维护它遇到的任何对象的类型正确性。即假设使用setValue()来设置一个列表属性。属性的类型实际上是List<Boolean>。SpEL认为列表的元素在放入之前需要转换成布尔值。下面的例子演示如何进行类型转换:

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 recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

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

(2) 解析器配置

可以通过使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)来配置SpEL表达式解析器。配置对象控制表达式组件的部分行为。例如,如果在数组或集合中建立索引,并且指定索引处的元素为null,那么SpEL可以自动创建该元素。这在使用由属性引用链组成的表达式时非常有用。如果在数组或列表中建立索引,并且指定的索引超出了数组或列表的当前大小的末尾,那么SpEL可以自动增长数组或列表以适应该索引。为了在指定的索引处添加元素,SpEL会在设置指定的值之前尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认构造函数,则将null添加到数组或列表中。如果没有内置或自定义转换器知道如何设置值,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

(3) SpEL编译

Spring Framework 4.1包含一个基本的表达式编译器。表达式通常是解释型的,这在求值时提供了很大的动态灵活性,但不能提供最佳性能。对于偶尔使用的表达式来说,没有问题。但是,当其他组件(如Spring Integration)使用时,性能可能非常重要,而且不需要动态性。
SpEL编译器就是为了解决这个问题。在求值期间,编译器生成一个在运行时包含表达式行为的Java类,并使用这个类来实现更快的表达式求值。由于表达式不需要接纳输入,编译器可在执行编译时使用表达式解释求值期间收集的信息。例如,它不能纯粹从表达式知道属性引用的类型,但在第一次解释求值期间,它会发现它是什么。当然,如果各种表达式元素的类型随着时间的变化而变化,那么基于这种派生信息的编译以后可能会造成麻烦。因此,编译最适合那些类型信息在重复求值时不会改变的表达式。
考虑下面的表达式:

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问、一些属性嵌套引用和数字操作,因此性能的提高可能非常显著。在一个运行了50000次迭代的微基准测试示例中,使用解释器计算需要75ms,而使用表达式的编译版本只需要3ms。

1)编译器配置

编译器默认情况下是不打开的,可以通过两种不同的方式打开:可以通过使用解析器配置过程(前面讨论过)打开,或者当SpEL使用嵌入到另一个组件中时,通过使用系统属性打开。
编译器有三种模式,org.springframework.expression.spel.SpelCompilerMode enum中有三种模式的表达:

  • OFF(默认):编译器被关闭。
  • IMMEDIATE:在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)。

2) 编译器限制

从Spring Framework 4.1开始,基本的编译框架已经成型。然而,该框架还不支持编译所有类型的表达式。最初的重点是可能在性能关键的上下文中使用的常见表达式。以下几种表达式目前无法编译:

  • 包含赋值的表达式
  • 依赖转换服务(conversion service)的表达式
  • 使用自定义解析器(resolvers)或访问器(accessors)的表达式
  • 使用选择或者投影的表达式

2. Bean定义中的表达式

可以在基于XML或者基于注解的BeanDefinition实例中使用SpEL表达式。在这两种情况下,定义表达式的语法都是#{<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>

应用程序上下文中的所有bean都可以作为具有通用bean名称的预定义变量使用。这包括用于访问运行时环境的标准上下文bean,如environment(类型为org.springframework.core.env.Environment),以及systemProperties和systemEnvironment(类型为Map<String,对象>)。下面的示例显示了对作为SpEL变量的systemProperties bean的访问:

<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 class FieldValueTestBean {

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

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

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

下面的示例展示了属性设置方法上的等效方法:

public 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 MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

3. SpEL语言参考

描述SpEL的工作原理。

(1) 常量表达式

支持的常量表达式类型有字符串、数值(int、real、hex)、布尔值和null。字符串用单引号分隔。要在字符串中放置单引号,请使用两个单引号字符。下面的例子显示了常量的简单用法。通常,它们不是单独使用,而是作为更复杂表达式的一部分使用——例如,在逻辑比较操作符的一侧使用常量。

ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();

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

(2) Properties、Arrays、Lists、Maps及Indexers

嵌套属性使用‘点’来访问,如下所示:

// evals to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);

允许属性名的第一个字母不区分大小写。因此,上面例子中的表达式可以写成Birthdate.Year + 1900和PlaceOfBirth.City。此外,可以通过方法调用选择性地访问属性,如getPlaceOfBirth().getCity()来代替placeOfBirth.city。
Arrays和Lists的内容是通过使用方括号表示法获得的,如下面的示例所示:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);

Maps的内容是通过在括号中指定文字键值来获得的。在下面的示例中,因为officer Map的键是字符串,所以我们可以指定字符串字面量:

// Officer's Dictionary

Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");

(3) 内联Lists

可以使用{}符号直接在表达式中表示Lists:

// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

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

(4) 内联Maps

还可以使用{key:value}表示法在表达式中直接表示Map,如下所示:

// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

{:}本身代表空Map。出于性能方面的原因,如果Map本身由固定的常量或其他嵌套常量结构(List或Map)组成,则创建一个常量Map来表示表达式(而不是在每次求值时构建一个新的Map)。Map键可以不使用引号。

(5) Array构造

可以使用熟悉的Java语法构建数组,提供一个初始化器,以便在构建时填充数组。如下所示:

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

注意:在构造多维数组时,当前不能提供初始化。

(6) 方法

可以使用典型的Java语法来调用方法。还可以调用常量上的方法。也支持变量参数。如下所示:

// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);

(7) 操作符

SpEL支持如下几种操作符:关系运算符、逻辑运算符、算数运算符、赋值运算符。

1) 关系运算符

标准关系运算符支持关系操作符(=、!、<、<=、>、>=)。如下所示:

// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

注意:应该避免把数字和null比较,其它值和null比较,其他值总大于null。
除标准关系运算符之外,SpEL支持instanceof及基于正则表达式的匹配操作符,如下所示:

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

//evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

注意原始类型,如int、long等,它们会立即被包装成包装器类型,所以1 instanceof T(int)计算为false,而1 instanceof T(Integer)计算为true。
还可以将每个符号操作符指定为等价的字母。这避免了使用的符号对嵌入表达式的文档类型具有特殊意义的问题:

对应的等价字母(不区分大小写)符号操作符
lt<
gt>
le<=
ge>=
eq==
ne!=
div/
mod%
not!

2) 逻辑运算符

SpEL支持如下的逻辑运算符:

  • and(&&)
  • or(||)
  • not(!)

使用举例如下所示:

// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

3)算数运算符

可以对数字和字符串使用加法运算符。只能对数字使用减、乘和除运算符。还可以使用模数(%)和指数幂(^)运算符。操作符优先级和标准操作符优先级相同。下面的例子展示了如何使用算数运算符:

// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0

// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0

// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1

// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21

4) 赋值运算符

要设置属性,使用赋值操作符(=)。这通常在对setValue的调用中完成,但也可以在对getValue的调用中完成。下面的例子显示了使用赋值操作符的两种方式:

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

(8) Types

可以使用特殊的T操作符来指定java.lang.Class的一个实例(类型)。静态方法也可以通过使用这个操作符来调用。StandardEvaluationContext使用TypeLocator来查找类型,而StandardTypeLocator(可以被替换)是在理解java.lang包的基础上构建的。这意味着T()引用java.lang中的类型不需要是完全限定的,但是所有其他类型引用必须是。下面的示例演示如何使用T操作符:

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);

(9) 构造器

可以使用new操作符调用构造函数。除了基本类型(int、float等)和String之外,您应该使用完全限定类名。下面的例子演示如何使用new操作符来调用构造函数:

Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

//create new inventor instance within add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);

(10) 变量

可以使用#variableName语法引用表达式中的变量。变量是通过在EvaluationContext实现中使用setVariable方法设置的。有效的变量名必须由一个或多个以下支持的字符组成:

  • 大小写字母
  • 数字
  • 下划线(_)
  • 美元符号($)

下面的例子展示了如何使用变量:

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"

#this及#root变量
#this变量总是被定义并指向当前的评估对象(根据该对象解析不合格的引用)。总是定义#root变量,并引用根上下文对象。尽管#this可能随着表达式的组件的计算而变化,但是#root总是指向根。下面的例子展示了如何使用#this和#root变量:

// 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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);

(11) 函数

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

Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);

下面例子显示了字符串反转函数的实现、注册及使用:

public abstract class StringUtils {

    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();
    }
}

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);

(12) Bean引用

如果计算上下文已经配置了bean解析器,则可以使用@符号从表达式查找bean。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);

要访问工厂bean本身,您应该使用&符号作为bean名称的前缀。如下所示:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);

(13) 三元操作符(If-Then-Else)

可以使用三元操作符在表达式内执行if-then-else条件逻辑。如下所示:

String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);

在本例中,布尔值false会返回字符串值’falseExp’。下面是一个更加复杂的例子:

parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression).getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

(14) Elvis操作符

Elvis操作符是三元操作符语法的缩写,在Groovy语言中使用。使用三元操作符语法,通常必须重复一个变量两次,如下面的示例所示:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

下面是Elvis写法:

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'

下面是一个稍微复杂一点的例子:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley

(15) 安全导航操作

安全导航操作符来自Groovy语言,用于避免NullPointerException。通常,当拥有对对象的引用时,可能需要在访问对象的方法或属性之前验证该引用是否为空。为了避免这种情况,安全导航操作符返回null而不是抛出异常。下面的示例演示如何使用安全导航操作符:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

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

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

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!

(16) 集合选择(Selection)

Selection是一个强大的表达式语言特性,它允许您通过选择源集合的条目将其转换为另一个集合。选择使用.?[selectionExpression]的语法。它对集合进行筛选并返回一个包含原始元素子集的新集合。如下面的示例所示:

List<Inventor> list = (List<Inventor>) parser.parseExpression("members.?[nationality == 'Serbian']").getValue(societyContext);

在Lists和Maps上都可以选择。对于Lists,根据每个单独的List元素来评估选择标准。对于Maps,选择标准将根据每个Map条目(Java类型map . entry的对象)进行评估。每个映射条目都有它的键和值,可以作为属性访问,以便在选择中使用。
下面的表达式返回一个新map,该map由原始map中条目值小于27的元素组成:

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

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

(17) 集合投影(Projection)

投影让集合驱动子表达式的求值,结果是一个新的集合。投影的语法是.![projectionExpression]。例如,假设我们有一个Inventors的list,但想要他们出生的城市list。实际上,我们想要根据Inventors的list的每一项求值“placeOfBirth.city”。下面的示例使用投影来实现这一点:

// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");

还可以使用Map来进行投影,在本例中,投影表达式针对映射中的每个条目进行计算(表示为Java map.entry)。Map的投影结果是一个List,该List包含对每个Map条目的投影表达式的求值。

(18) 表达式模板

表达式模板允许将常量与一个或多个求值块混合在一起。每个求值块都由可定义的前缀和后缀字符分隔。常见的选择是使用#{}作为分隔符,如下面的示例所示:

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

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

public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}

4. 前面举例中所用例子涉及的相关类实现

package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

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

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

    private String city;
    private String country;

    public PlaceOfBirth(String city) {
        this.city=city;
    }

    public PlaceOfBirth(String city, String country) {
        this(city);
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String s) {
        this.city = s;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

    private String name;

    public static String Advisors = "advisors";
    public static String President = "president";

    private List<Inventor> members = new ArrayList<Inventor>();
    private Map officers = new HashMap();

    public List getMembers() {
        return members;
    }

    public Map getOfficers() {
        return officers;
    }

    public String getName() {
        return name;
    }

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

    public boolean isMember(String name) {
        for (Inventor inventor : members) {
            if (inventor.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }
}

标签:parseExpression,String,核心技术,Spring,parser,getValue,学习,class,表达式
来源: https://blog.csdn.net/weixin_43106572/article/details/111956861

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有