首页 > 代码库 > Spring 4 官方文档学习(五)核心技术之SpEL

Spring 4 官方文档学习(五)核心技术之SpEL

1、介绍

SpEL支持在runtime 查询、操作对象图。

2、功能概览

英文 中文
Literal expressions 字面值表达式
Boolean and relational operators 布尔和关系操作符
Regular expressions  正则表达式
Class expressions 类表达式
Accessing properties, arrays, lists, maps 访问properties、arrays、lists、maps
Method invocation 方法调用
Relational operators 关系操作符
Assignment 赋值
Calling constructors 调用构造器
Bean references bean引用
Array construction 构建数组
Inline lists 内联lists
Inline maps 内联maps
Ternary operator 三元操作符
Variables 变量
User defined functions 用户定义的功能
Collection projection 集合投影
Collection selection 集合选择
Templated expressions 模板化表达式

3、使用SpEL的API来evaluate 表达式

前提,搭建一个小环境,将Spring的基本jar包放进去。 -- 最简单的办法,使用STS新建一个Spring Boot项目,选择Spring Core模块即可。

然后,使用JUnit测试下面的代码:

package cn.larry.demo;import org.junit.Test;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;public class TheTest {    @Test    public void run() {        ExpressionParser parser=new SpelExpressionParser();        String str="‘Hello World‘";                Expression exp = parser.parseExpression(str);//        Object value = http://www.mamicode.com/exp.getValue();        String value = http://www.mamicode.com/exp.getValue(String.class);                System.out.println(value);    }}

你最有可能用到的SpEL相关的类和接口位于 org.springframework.expression 包及其子包,以及 spel.support 包中。

ExpressionParser接口,负责解析(parse)表达式字符串。

Expression接口,负责evaluate前面定义的表达式字符串 -- 是指ExpressionParser解析出来的吗?

 

再来看看SpEL如何调用方法:

// demo for calling method@Testpublic void run1() {    ExpressionParser parser = new SpelExpressionParser();    String str = "‘Hello World‘.concat(‘!!!‘)";    Expression exp = parser.parseExpression(str);    Object value = exp.getValue();    System.out.println(value);}

 

还有如何获取property -- 前提是JavaBean:

// demo for accessing property of JavaBean@Testpublic void run2() {    ExpressionParser parser = new SpelExpressionParser();    String str = "‘Hello World‘.bytes";    Expression exp = parser.parseExpression(str);    byte[] value = http://www.mamicode.com/exp.getValue(byte[].class);    System.out.println(value);}

 

还支持获取nested property -- 前提是JavaBean:

// demo for accessing nested property of JavaBean@Testpublic void run3() {    ExpressionParser parser = new SpelExpressionParser();    String str = "‘Hello World‘.bytes.length";    Expression exp = parser.parseExpression(str);    int value = http://www.mamicode.com/exp.getValue(int.class);    System.out.println(value);}

调用构造器:

//demo for calling constructor@Testpublic void run4() {    ExpressionParser parser = new SpelExpressionParser();    String str = "new String(‘Hello World‘).toUpperCase()";    Expression exp = parser.parseExpression(str);    Object value = exp.getValue();    System.out.println(value);}

 

The more common usage of SpEL is to provide an expression string that is evaluated against a specific object instance (called the root object).

有两种用法,具体选择哪种取决于你每次evaluate the expression时对象是否会发生改变。来看看两种代码:

/** * The StandardEvaluationContext is relatively expensive to construct and during repeated usage  * it builds up cached state that enables subsequent expression evaluations to be performed more quickly.  * For this reason it is better to cache and reuse them where possible, rather than construct a new one for each expression evaluation. */@Testpublic void run5() {    GregorianCalendar cal = new GregorianCalendar();    cal.set(1856, 7, 9);    Inventor inventor = new Inventor("Nicolas Tesla", cal.getTime(), "serbian");    SpelExpressionParser parser = new SpelExpressionParser();    Expression expression = parser.parseExpression("name");    // 利用目标对象(root object)构造一个context。开销相对较高,建议仅用于不常改变的对象。    EvaluationContext evaluationContext = new StandardEvaluationContext(inventor);        inventor.setName("Newton"); // 还是会起作用        // 从指定的context中获取本表达式对应的值。    String value =http://www.mamicode.com/ (String) expression.getValue(evaluationContext);    System.out.println(value);}// 如果root object可能发生改变,不建议使用其创建一个context,因为开销较高。应该如下:@Testpublic void run6() {    GregorianCalendar cal = new GregorianCalendar();    cal.set(1856, 7, 9);    Inventor inventor = new Inventor("Nicolas Tesla--", cal.getTime(), "serbian");    SpelExpressionParser parser = new SpelExpressionParser();    Expression expression = parser.parseExpression("name");    // 直接从目标对象中获取本表达式对应的值。内部还是会创建一个context。    // the expression evaluation infrastructure creates and manages a default evaluation context internally    String value =http://www.mamicode.com/ (String) expression.getValue(inventor);      System.out.println(value);        inventor.setName("Newton"); // 也起作用了    value = (String) expression.getValue(inventor);      System.out.println(value);}

这里的关键在于:Expression是从指定上下文中获取相应的内容。粗略的说,Expression指定了要获取的property name,但还需要指定从什么地方获取该property value -- 是从EvaluationContext中获取的。

上面两个案例,run5() 是用目标对象(inventor)直接构造了一个EvaluationContext StandardEvaluationContext,以供Expression从中获取值; run6() 则是让Expression直接从目标对象(inventor)中获取值 --但实际上内部还是构造了一个EvaluationContext。

二者的不同之处在于StandardEvaluationContext开销较大。

The StandardEvaluationContext is relatively expensive to construct and during repeated usage, it builds up cached state that enables subsequent expression evaluations to be performed more quickly. For this reason it is better to cache and reuse them where possible, rather than construct a new one for each expression evaluation.

In standalone usage of SpEL there is a need to create the parser, parse expressions and perhaps provide evaluation contexts and a root context object. However, more common usage is to provide only the SpEL expression string as part of a configuration file, for example for Spring bean or Spring Web Flow definitions. In this case, the parser, evaluation context, root object and any predefined variables are all set up implicitly, requiring the user to specify nothing other than the expressions. 

 

3.1、EvaluationContext 接口

当evaluate an expression以获取properties、methods、fields以及帮助执行类型转换时,使用该接口。现成的实现类是 StandardEvaluationContext,利用了反射来操作对象、缓存 java.lang.reflect.Method、java.lang.reflect.Field和java.lang.reflect.Constructor的实例以增进性能。

你可以:

指定目标对象(root object):使用无参构造,再调用setRootObject();使用有参构造。 

也可以指定Expression中使用的变量和方法:setVariable()、registerFunction()。

还可以register custom ConstructorResolvers, MethodResolvers, and PropertyAccessors to extend how SpEL evaluates expressions。

详见JavaDoc。

 

类型转换

默认情况下,SpEL使用Spring core中的conversion service。支持泛型。

示例代码:

// SpEL默认使用Spring core的conversion service。默认支持泛型。@Testpublic void run8() {    class Simple {        public List<Boolean> booleanList = new ArrayList<Boolean>();    }    Simple simple = new Simple();    simple.booleanList.add(true);    StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);    SpelExpressionParser parser = new SpelExpressionParser();    // 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(simpleContext, "false");// 不止能获取,还能设置。    // b will be false    Boolean b = simple.booleanList.get(0);        System.out.println(b);}

 

3.2、Parser configuration

通过设置Parser,可以改变某些默认的行为。典型的,例如 通过索引访问数组或集合,如果返回的是null,可以让它自动创建元素(empty element)。

例如,当你越界访问数组或list的元素时,可以让数组或list的长度自动增长!!!

代码如下:

// demo for parser configuration@Testpublic void run9() {    class Demo {        public List<String> list;    }    // Turn on:    // - auto null reference initialization    // - auto collection growing    SpelParserConfiguration config = new SpelParserConfiguration(true, true); // 关键,见JavaDoc    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    System.out.println(demo.list);}

 

3.3、SpEL compilation (略)

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#expressions-spel-compilation

 

请注意,上面的测试代码都是直接调用的API,所以不必启动环境(没有容器)。但下面的不同,需要启动环境(或者模拟环境RunWith)。


4、支持定义bean definition的表达式

SpEL可以用在基于XML或基于注解的配置元数据以定义BeanDefinition。语法: #{ <expression string> }。

4.1、基于XML的配置

使用expression设置property或者constructor-args:

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

上面的 T 表示源自jdk。

 

另外,系统已经预定义了 systemProperties,所以可以直接引用(官方文档用的是user.region,实际上是不存在的):

<bean id="person" class="cn.larry.demo.Person">    <property name="name" value="#{ systemProperties[‘user.name‘] }"/>    <!-- 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>

 

4.2、基于注解的配置

@Value 注解可以放在fields、methods以及method/constructor的参数上,以注入默认值。

示例代码:

 

 

输出了下SystemProperties的keySet:

[java.runtime.name, sun.boot.library.path, java.vm.version, java.vm.vendor, java.vendor.url, path.separator, java.vm.name, file.encoding.pkg, user.country, user.script, sun.java.launcher, sun.os.patch.level, PID, java.vm.specification.name, user.dir, java.runtime.version, java.awt.graphicsenv, org.jboss.logging.provider, java.endorsed.dirs, os.arch, java.io.tmpdir, line.separator, java.vm.specification.vendor, user.variant, os.name, sun.jnu.encoding, spring.beaninfo.ignore, java.library.path, java.specification.name, java.class.version, sun.management.compiler, os.version, user.home, user.timezone, java.awt.printerjob, file.encoding, java.specification.version, java.class.path, user.name, java.vm.specification.version, sun.java.command, java.home, sun.arch.data.model, user.language, java.specification.vendor, awt.toolkit, java.vm.info, java.version, java.ext.dirs, sun.boot.class.path, java.awt.headless, java.vendor, file.separator, java.vendor.url.bug, sun.io.unicode.encoding, sun.cpu.endian, sun.desktop, sun.cpu.isalist]

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注:

如果不知道Spring Boot,请先看这个:Spring Boot学习。

如果不知道STS,请先看这个:Spring Tools Suite (STS) 简介。--需要先了解Spring Boot。

嗯,篇幅都很短,半个小时基本可以搞定,很简单的东西。

Spring 4 官方文档学习(五)核心技术之SpEL