Spring表达式语言(简称SpEL)是强大的表达式语言,它支持在运行时查询和操纵对象图表。语法与统一EL类似可是提供了额外的功能,最引人注目的是方法调用和基本字符串模版功能。java
尽管有另外几种Java表达式语言可用,例如OGNL、MVEL和JBoss EL,Spring表达式语言被建立用于提供给Spring社区一个单一的支持良好的表达式语言,它能够用于Spring portfolio中的所用产品。它的语言特性被Spring portfolio中的项目的需求驱动,包括基于Eclipse的Spring Tool Suite中的代码补齐支持的工具需求。也就是说,SpEL是基于技术不可知的API,容许在须要时集成其余表达式语言实现。正则表达式
尽管SpEL做为Spring portfolio中表达式求值的基础,它不直接与Spring绑定而且能够被独立使用。为了自持,本章中的许多例子使用SpEL,就像它是一种独立的表达式语言。这须要建立一些启动基础设施类例如解析器。大多数Spring用户不须要处理这种基础设施,而只会编写表达式字符串用于求值。这个典型应用的一个例子是集成SpEL建立基于XML或者注解的bean定义,这会在“建立bean定义的表达式支持”一节中展现。spring
本章覆盖了表达式语言的特性、API和语法。在一些地方,Inventor和Inventor的Society类被用于表达式求值的目标对象。这些类的声明和用来填充它们的数据在本章的最后被列出。express
表达式语言支持下面的功能:编程
本节介绍SpEL接口和表达式语言的简单实用方法。完整的语言参考能够在“语言参考”一节中找到。数组
下面的代码介绍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包中。app
ExpressionParser接口用于解析一个表达式字符串。在这个例子中,表达式字符串是包围着单引号的一个字符串文字。Expression接口用于对预先定义好的表达式字符串求值。调用parser.parseExpression和exp.getValue可能会分别抛出ParseException和EvaluationException。框架
SpEL支持许多特性,例如调用方法、访问属性和调用构造函数。
做为调用方法的一个例子,咱们在字符串字面值上调用concat方法。
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); String message = (String) exp.getValue();
消息的值如今是"Hello World!"。
做为访问JavaBean属性的一个例子,字符串的bytes属性能够以下调用:
ExpressionParser parser = new SpelExpressionParser(); // 调用 'getBytes()' Expression exp = parser.parseExpression("'Hello World'.bytes"); byte[] bytes = (byte[]) exp.getValue();
SpEL也支持使用辨准的点号访问和设置嵌入属性,例如prop1.prop2.prop3。
公有属性也能够被访问。
ExpressionParser parser = new SpelExpressionParser(); //调用 '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> T getValue(Class<T> desiredResultType)的用法。使用这个方法避免了转换表达式的值到指望类型的须要。若是值不能被转换为T或者不能使用注册的类型转换器转换,将抛出EvaluationException。
SpEL更广泛的用法是提供表达式字符串用于对特定的对象实体(称为根对象)求值。这里有两种方法,要选择哪一个取决于表达式求值的对象是否会随每次调用而更改。在下面的例子中,咱们获取Inventor类的对象的name属性。
// 建立并设置一个calendar GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); // 构造函数的参数是name, birthday和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属性被求值。若是根对象不太可能改变,就使用这种机制,它能够在上下文中简单的设置一次。若是根对象可能不断的改变,能够在每次调用getValue时提供这个根对象,以下面的例子所示:
// 建立并设置一个calendar GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); // 构造函数的参数是name, birthday和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);
在这种状况下Inventor tesla被直接提供给getValue方法而且表达式求值的基础设置在内部建立和管理一个默认的求值上下文,不须要咱们提供。
StandardEvaluationContext的构建是相对昂贵的而且在重复使用的过程当中它会创建缓存状态以便可以更快的执行后续表达式的求值。所以,最好在可能的状况下缓存和重用他们,而不是为每一个表达式求值构建一个新的。
在一些状况下,可能须要使用配置过的求值上下文而且在每次调用getValue时仍然提供不一样的根对象。getValue容许在同一个调用中指定二者。在这些状况下,调用时被传递的根对象会覆盖任何求值上下文指定的对象。(就是使用getValue(EvaluationContext context, Object rootObject)方法)
在独立使用SpEL时,须要建立解析器,解析表达式而且提供求值上下文和根上下文对象。然而,更广泛的使用场景是仅提供SpEL表达式字符串做为配置文件的一部分,例如用于Spring bean或Spring Web流定义。在这种状况下,求值上下文,根对象和任何预约义的变量被隐式建立,不须要用户指定除表达式外的任何东西。
做为最后一个例子,用上一个例子中的Inventor对象展现布尔运算符的使用。
Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); boolean result = exp.getValue(context, Boolean.class); // 求值为true
当对一个表达式求值时使用EvaluationContext接口来解析属性、方法、字段和帮助执行类型转换。开箱即用的实现StandardEvaluationContext,使用反射来操做对象,经过缓存java.lang.reflect.Method、java.lang.reflect.Field和java.lang.reflect.Constructor接口来提升效率。
StandardEvaluationContext时经过setRootObject()方法或者传递给构造函数来参数来指定根对象的地方。也能够经过setVariable()和registerFunction()方法指定将会在表达式中使用的变量和函数。变量和函数的使用在语言参考的变量和函数一节讨论。也能够经过StandardEvaluationContext注册自定义的ConstructorResolver、MethodResolver和PropertyAccessor来扩展SpEL求值表达式的行为。参考这些类的javadoc来获取详细信息。
默认的,SpEL使用Spring核心中可用的转换服务(org.springframework.core.convert.ConversionService)。这个转换服务包含许多内建的用于常见类型的转换器,可是也彻底可扩展的以便添加两种类型间的自定义转换。另外它有感知范型的关键能力。这意味着当在表达式中使用范型类型,SpEL会尝试转换以维护遇到的任何对象的类型正确性。
在实践中这意味着什么?假设使用setValue()给一个List属性赋值。属性的类型实际上时List<Boolean>。SpEL将会识别到这个列表的元素须要在被添加到其中前转换为布尔类型。一个简单的例子:
class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } Simple simple = new Simple(); simple.booleanList.add(true); StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); // 此处false做为字符串被传入。 //SpEL和转换服务将会正确的识别到这里须要一个Boolean类型而且转换它。 parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); // b 将会为false Boolean b = simple.booleanList.get(0);
可使用一个解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)配置SpEL表达式的解析器。配置对象控制一些表达式组件的行为呢。例如,若是在数组或集合中检索而且指定索引的元素为null,能够自动建立元素。当使用的表达式由属性引用的链构成,这是很是有用的。若是在数组或集合中检索而且指定索引超出了数组或集合如今的尺寸,能够自动增加尺寸来容纳索引。
class Demo { public List<String> list; } // 打开: // - null应用自动初始化 // - 集合自动扩容 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如今是一个有四个元素的集合 // 每一个元素是一个空字符串
也能够配置SpEL表达式编译器的行为。
Spring Framework 4.1包含了基础的表达式编译器。表达式一般被视为在求值过程当中提供了大量的动态灵活性,但不能提供最佳性能。对于临时的表达式使用是良好的,可是当被像Spring Integration这样的其余组件使用,性能是很是重要的而且没有动态的实际须要。
新的SpEL编译器用于知足这种需求。编译器将会在求值期间即时生成一个真正的Java类,它体现了表达式的行文并使用它来实现更快的表达式求值。因为缺乏表达式中的类型信息,编译器在执行编译时使用在表达式的解释性评估期间收集的信息。例如,它没法从表达式中得到一个属性引用的 类型,可是在第一次解释性求值过程当中会发现它时什么。固然,若是各类表达式元素的类型随着时间的推移而变化,那么基于这些信息的编译可能在稍后致使麻烦。所以,编译最适合于重复糗事时类型信息不会改变的表达式。
对于一个以下的基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
包含了数组的方法,一些属性的引用和数值操做,性能的增益能够很是明显。在50000次迭代的微型基准运行实例中,使用解释器须要花费75ms而使用编译版本的表达式只须要3ms。
编译器默认不会被打开,可是有两种方法打开它。使用上面介绍的解析器配置处理或者当在另一个组件中嵌入使用SpEL时经过系统属性。这一节讨论这两种方法。
明白编译器能够有几种运行模式很是重要,模式在一个枚举中(org.springframework.expression.spel.SpelCompilerMode)。模式以下:
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.complier.mode能够被设定为SpelCompilerMode枚举值中的一种(off,immediate或者mixed)。
Spring Framework 4.1引入了基本编译框架。然而,框架尚未支持编译每一种表达式。最初的重点是可能在性能关键环境中使用的常见表达式。下面这些种类的表达式如今没法被编译:
更多类型的表达式在未来会被编译。
SpEL表达式能够与XML或基于注解的配置元数据一块儿使用来定义BeanDefinition。在这两种状况下,定义表达式的语法格式为#{<表达式字符串>}。
属性或者构造函数参数值可使用表达式设置以下:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- 其余属性 --> </bean>
systemProperties变量被预约义,因此像下面这样在表达式中使用。注意在此上下文中,不须要使用#符号做为预约义变量的前缀。
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <!-- 其余属性 --> </bean>
也可使用name引用其余bean属性,例如:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- 其余属性 --> </bean> <bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> <!-- 其余属性 --> </bean>
@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 MovieRecommender { private String defaultLocale; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; this.defaultLocale = defaultLocale; } // ... }
字面表达式支持的类型有字符串、数值(整数、实数、十六进制)、布尔型和null。字符串由单引号分隔。要将一个单引号自己放在字符串中,使用两个单引号。
下面的列表展现字面表达式的简单使用方法。一般,它们不会像这样使用,而是做为更复杂表达式的一部分,例如在逻辑比较运算分的一侧使用文字。
ExpressionParser parser = new SpelExpressionParser(); // 求值为"Hello World" String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); // 求值为 2147483647 int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); Object nullValue = parser.parseExpression("null").getValue();
数值支持负号、指数符号和十进制点号的使用。默认的,使用Double.parseDouble()解析实数。
使用属性引用浏览是容易的:只需使用点好来指示嵌套的属性值。Inventor类的实例pupin和tesla,使用“例子中使用的类”一节列出的数据填充。为了“向下”浏览,获取Tesla的出生年份和Pupin的出生城市,使用一下的表达式。
// 求值为1856 int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
属性名的首字母不区分大小写。数组和列表的内容使用方括号符号获取。
ExpressionParser parser = new SpelExpressionParser(); // Inventions数组 StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); // 求值为 "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue( teslaContext, String.class); // Members列表 StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); // 求值为 "Nikola Tesla" String name = parser.parseExpression("Members[0].Name").getValue( societyContext, String.class); // 列表和数组 navigation // 求值为"Wireless communication" String invention = parser.parseExpression("Members[0].Inventions[6]").getValue( societyContext, String.class);
Map的内容经过在括号内指定文字的键值得到。在下面的例子中,由于Officers map的键是字符串,能够指定字符串字面值。
// Officer's Dictionary Inventor pupin = parser.parseExpression("Officers['president']").getValue( societyContext, Inventor.class); // 求值为 "Idvor" String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue( societyContext, String.class); // 设置值 parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue( societyContext, "Croatia");
列表可使用{}符号直接在表达式中表示。
// 求值为一个Java列表包含四个数值 List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
{}自身表示一个空列表。出于性能的缘由,若是列表自己彻底由固定文字组成,则会建立一个常量列表来表示表达式,而不是在每次求值时构建一个新列表。
映射也可使用{key:value}符号直接在表达式中表示。
// 求值为一个包含两个键值对的Java映射 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);
{:}自身表示一个空映射。出于性能的缘由,若是映射自己彻底由固定文字或其余关联的常熟结构(列表或映射)组成,则会建立一个常量列表来表示表达式,而不是在每次求值时构建一个新列表。引用映射键是可选的,上面的示例中没有使用引用的键。
可使用与Java类似的语法构建数组,能够选择提供初始化器以在构造的时间填充数组。
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); // 使用初始化器构造数组 int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); // 多维数组 int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
如今在构造多维数组时不容许使用初始化器。
使用典型的Java编程语法调用方法。也能够在字面值上调用方法,同时也支持变量。
// 字符串字面值, 求值为"bc" String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class); // 求值为 true boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( societyContext, Boolean.class);
关系运算符:相等,不等,小于,小于等于,大于,大于等于支持使用标准运算符。
// 求值为 true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); // 求值为 false boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); // 求值为 true boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
大于/小于与null比较遵循一个简单原则:null被认为是没有(即,不是0)。所以,任何其余值老是大于null(X > null总为true)而且没有任何其余值比null小(X < null总为false)。若是更倾向于使用数值比较,请避免基于数字的null比较而使用与0的比较(例如 X > 0 或 X < 0)。
除了标准关系操运算符SpEL支持instanceof和机遇正则表达式的matches运算符。
// 求值为false boolean falseValue = parser.parseExpression( "'xyz' instanceof T(Integer)").getValue(Boolean.class); // 求值为true boolean trueValue = parser.parseExpression( "'5.00' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); //求值为false boolean falseValue = parser.parseExpression( "'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
当心使用基本类型,由于它们会当即被装箱为包裹类型,因此 1 instanceof T(int)求值为false而 1 instanceof T(Integer)求值为true。
每一个符号运算符也能够被替换为纯粗的字母等价形式。这避免了使用的符号在嵌入表达式的文档类型中具备特殊含义的问题(例如,XML文档)。文本的等价形式以下:lt (<), gt (>), le (⇐), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!)。它们是不区分大小写的。
逻辑运算符中支持与、或和非。它们的用法展现以下。
/ -- 与 -- // 求值为 false boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); // 求值为 true String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- 或 -- // 求值为 true boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); // 求值为 true String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- 非 -- // 求值为 false boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); // -- 与非 -- String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
加运算符能够在数值和字符串使用。减法、乘法和除法只能在数值上使用。此外还支持求模(%)和指数幂(^)。执行标准运算符优先级。这些运算符展现以下:
// 加 int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression( "'test' + ' ' + 'string'").getValue(String.class); // 'test string' // 减 int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // 乘 int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // 除 int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // 模 int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // 运算符优先级 int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
使用赋值运算符设置属性。这一般经过调用setValue完成,但也能够在调用的getValue内部完成。
Inventor inventor = new Inventor(); StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); // alternatively String aleks = parser.parseExpression( "Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
特殊的T运算符可用于指定java.lang.Class(类型)的实例。也可使用此运算符调用静态方法。StandardEvaluationContext使用TypeLocator查找类型,而且StandardTypeLocator(能够被替换)构建了对java.lang包的理解。这意味着T()指向java.lang包中的类型不须要使用彻底限定符,可是全部其余类型的引用必须使用。
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);
使用new运算符能够调用构造函数。除了基本类型和字符串(int、float等)意外的其余类型必须使用彻底限定的类名。
Inventor einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") .getValue(Inventor.class); //在列表的add方法中建立新的inventor实例 p.parseExpression( "Members.add(new org.spring.samples.spel.inventor.Inventor( 'Albert Einstein', 'German'))").getValue(societyContext);
使用#variableName语法在表达式中引用变量。使用StandardEvaluationContext上的setVairable方法设置变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); StandardEvaluationContext context = new StandardEvaluationContext(tesla); context.setVariable("newName", "Mike Tesla"); parser.parseExpression("Name = #newName").getValue(context); System.out.println(tesla.getName()) // "Mike Tesla"
变量#this老是被定义并引用当前求值对象(针对哪一个非限定引用被解析)。变量#root老是被定义并引用根上下文对象。尽管#this可能由于被求值的表达式组件而发生变化,可是#root老是引用根对象。
// 建立整数数组 List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); // 建立解析器而且设置变量primes为整型数组 ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("primes",primes); // list中的全部基本数值大于10 (u使用选择器 ?{...}) // 求值为 [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( "#primes.?[#this>10]").getValue(context);
能够经过注册能够在表达式字符串中调用的用户自定义函数扩展SpEL。使用StandardEvaluationContext的以下方法注册函数:
public void registerFunction(String name, Method m)
一个Java方法的引用提供函数的实现。例如,一个工具方法反转字符串的使用方法以下所示:
public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(); for (int i = 0; i < input.length(); i++) backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } }
而后使用求值上下文注册这个方法而且能够在表达式字符串中使用。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.registerFunction("reverseString", StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class })); String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class);
若是求值上下文被配置了一个bean极细气,能够在表达式中使用@符号查找bean。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); //在求值过程当中它会调用MyBeanResolver的resolve(context, "foo")方法 Object bean = parser.parseExpression("@foo").getValue(context);
为了获得工厂方法自己,bean名字须要添加&前缀而不是@。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // 在求值过程当中它会调用MyBeanResolver的resolve(context, "foo")方法 Object bean = parser.parseExpression("&foo").getValue(context);
能够在表达式中使用三元运算符表示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"
请查看下一节的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(String.class); System.out.println(name); // 'Unknown'
下面是一个更复杂的例子。
ExpressionParser parser = new SpelExpressionParser(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); StandardEvaluationContext context = new StandardEvaluationContext(tesla); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); System.out.println(name); // Nikola Tesla tesla.setName(null); name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); System.out.println(name); // Elvis Presley
安全的导航运算符来自Groovy语言,用于避免NullPointerException。通常状况下,当你引用一个对象,你也许须要在访问对象的方法或者属性以前验证它是否为空。为了不这个操做,安全导航运算符会简单的返回null替代抛出异常。
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 - 不会抛出NullPointerException!!!
Elvis运算符能够用于在表达式中设置默认值,例如一个一个@Value表达式中:@Value("#{systemProperties['pop3.port'] ?: 25}")。这将注入系统属性pop3.port(若是已定义)或25(若是不是)。
选择器是一个强大的表达式语言特性,它运行你经过选择源列表中的元素将源列表转换为另外一个一个列表。
选择器使用语法.?[选择器表达式]。它将锅略列表而且返回一个包含原始元素子集的新列表。例如,选择器容许咱们简单的获取一个塞尔维亚发明家列表:
List<Inventor> list = (List<Inventor>) parser.parseExpression( "Members.?[Nationality == 'Serbian']").getValue(societyContext);
列表和映射均可以使用选择器。在前一种状况下,根据每一个单独列表元素求值选择标准;同时针对映射,根据每一个映射键值对(Java Map.Entry类型)求值选择标准。
映射的键值对能够做为属性在选择器中访问。
下面的表达式会返回一个新的映射由原始映射中值小于27的键值对组成。
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回全部选定的元素,还能够检索第一个或最后一个值。要获取与选择器匹配的第一个键值对语法为^[...];同时获取匹配的选择器的最后一条,语法为$[...]。
投影容许集合驱动子表达式的求值,结果是一个新的集合。投影的语法是![projectionExpression]。最容易理解的例子,假设咱们有一个发明家的列表,但但愿获取他们出生的城市的列表。高效的,咱们对发明人列表中的每一个条目的"placeOfBirth.city"求值。使用投影:
// 返回 ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
映射也能够被用于驱动投影而且在这种状况下会根据Map中的每一个键值对对投影表达式求值。映射投影的结果是由对每一个映射键值对的投影表达式的求值组成的列表。
表达式模板容许字面文本与一个或多个求值模块混合在一块儿。每一个求值模块用你定义的前缀和后缀自负分隔,一个广泛的选择是使用#{ }做为分隔符。例如:
String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // 求值为 "random number is 0.7038186818312008"
字符串经过链接字面文本"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; } }
Inventor.java
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; } }
PlaceOfBirth.java
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; } }
Society.java
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; } }