Spring Expression Language(SpEL) spring表达式语言

10.Spring Expression Language(SpEL) spring表达式语言

10.1 介绍

SPEL语言是一个很是强力的支持运行时查询和操做对象图谱的语言.这个语言语法和传统EL表达式类似,但提供了许多额外功能,最重要的是函数调用和基本字符串模板函数;html

虽然还有其余的java表达式语言,如OGNL,MVEL,JBoss EL等,但SPEL是由spring社区独家建立并支持的,并能在spring产品集中的全部产品里使用的语言.它的语言功能是基于spring项目集的产品需求驱动,也包括工具的需求,这块主要是指基于eclipse的Spring Tool Suite.也就是说,SpEl是基于技术无关的API,并容许其余须要加入的语言实现加入并将之集成.java

SpEL服务是spring项产品中表达式工具的基础,它没有直接与spring绑定并可独立使用.为了可以自适用,本章中全部例子在使用SpEL时都看成独立的表达语言.这要求建立一系列的如parser等的启动组件类.大部分的spring用户不须要处理这些组件,仅需将程序员的表达式字符串进行解析.典型使用的例子是将SpEL集成到xml或基于注解的bean定义里,详情查看 Expression support for defining bean definitions;程序员

本章主要介绍该语言的主要功能,API和语法.有些地方,发明和发明新类将做为表达目标的替换对象.操做这些类的声明和数据,已在本章末尾列明.正则表达式

10.2 功能概览

有如下功能:spring

  • Literal expressions 字面表达式
  • Boolean and relational operators 布尔和关系运算符
  • Regular expressions 正则表达式
  • Class expressions 类表达式
  • Accessing properties,arrays,lists,maps
  • Method invocation 方法调用
  • Relational operators 关系运算
  • Assignment 参数
  • Calling constructors 调用构造器
  • Bean references bean引用
  • Array construction 数组构造器
  • Inline lists 内联lists
  • Inline maps 内嵌mps
  • Ternary operator 三元运算符
  • Variables 变量
  • User defined functions 用户定义函数
  • Collection projection 集合投影
  • Collection selection 集合筛选
  • Templated expressions 模板表达式

10.3 使用SpEL接口的表达式替换

本节介绍了SpEL接口的简单使用和它的表达语言.该语言的文档在Language Reference中可见. 下面的代码介绍了SpEL API如何处理字面字符串表达式'HELLO World'.express

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

这个message变量的值是简单的'Hello World'.编程

你最经常使用的SpEL类和接口在包org.springframework.expression和他的子包spel.support里.数组

ExpressionParser主要负责转化一个字符串表达式 .上个例子中这个表达式字符串是一个有单引号包围的字符串字面值.Expression接口负责转化以前定义好的表达式字符串.当分别调用parser.parserExpression,exp.getValueException时,会抛出两种异常:ParseException和EvaluationException缓存

SpEL还支持其余功能,例如调用方法,访问属性,调用构造器.安全

下面是一个方法调用的例子,你能够调用字面值字符串的concat方法.

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

消息的值如今是'Hello World!'.

下个例子是调用string的属性Bytes,以下;

ExpressionParser parser =new SpelExpressionParser();

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

Spel还支持使用点符号来访问内嵌的属性,例如 prop1.prop2.prop3,还能够设置属性的值.公共的字段也能够访问:

ExpressionParser parser = new SpelExpressionParser();

//invokes 'getBytes().length'
Expression exp=parser.parserExpression("'Hello World'.bytes.length");
String message=exp.getValue(String.class);

记住泛型方法public<T> getValue(Class<T> desiredResultType)的使用.使用该方法能够省去表达式的值向目标类型转化的过程.若是该值没法转化成T类型或者没法经过已注册的转化器转化,则会抛出EvaluationException的异常.

从实例里来获取值转化

从一个特定实例里获取值转化是比较常见的用法(这个实例也叫根对象).这里有两个方法,取决于你要转化表达式值的对象是否会常常变化.下面是从一个转化类实例里转化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");

EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

在最后一行,字符串变量name将会被设置为"Nikola Tesla".

若是该特定对象的值常常变化,则须要直接从对象里获取值:

// 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);

在此状况下getValue直接提供给getValue和表达式组件的转化器tesla建立和管理一个相应的默认转化上下文--这个上下文不是必需的.

StandardEvaluationContext相对来讲构造比较昂贵,在重复环境下,它被构形成缓存状态,这样能够保证经常使用的表达式转化工做更快.所以最好尽量的缓存和重用它们,而不是为每一个表达式转化构造一个新的.

在一些场景下,你最好使用一个配置的转化上下文,并会为每一个getValue调用提供不一样的根对象.getValue容许指定相同的调用.在这个场景下被调用的根对象被认为是在转化上下文中重写了特定的值.

[注意]

在单独使用SpEL时,这里须要建立parser,parser expression,可能还有转化上下文以及根对象.可是,更常见的用法是把SpEL表达式做为配置文件的一部分,例如spring bean或者spring Web Flow 定义.在这种状况下,parser,evaluation context,根对象和其余已定义对象都是未明确的定义,所以要求用户只需指定表达式.

最后一个入门例子,是使用上个例子中的转化器对象展现的布尔操做符的例子;

Expression exp=parser.parserExpression("name='Nikola Tesla'");
boolean result =exp.getValue(contxt,Boolean.class) //evaluates to true

10.3.1 the EvaluationConext interface

接口EvaluationContext会被使用当转化一个表达式为相应属性,方法,字段,或执行类型转化.其一个开箱即用的实现,StandardEvaluationContext,使用反射操做对象,缓存了java.lang.reflect.Method,java.lang.reflect.Field,以及java.lang.reflect.Constructor实例来应付更多的操做.

StandardEvaluationContext能够经过setRootObject()或把根对象加入构造器来指定其要转换的根对象.你能够经过setVariable()和registerFunction()来指定表达式可能用到的函数和变量.变量和方法使用在变量和函数一节里有描述.你能够在StandardEvaluationContext中注册自定义ConstructorResolvers,MethodResolvers,PropertyAccessors来扩展SpEL转化表达式.能够参看这些类的javaDoc查看更多细节.

类型转化 Type Conversion

SpEL默认使用spring core(org.springframework.core.convert.ConversionService)中的转化服务.这个转化服务带着许多内置的转化器,但它有很好的扩展性,你能够添加不一样类型之间的自定义转换.另外他的核心功能是泛型感知.这意味着当表达式中有泛型类型时,SpEL会尝试转化它遇到的任何对象以保证类型正确.

这个在实际中意味着什么?尝试用setValue()方法指定一个List的属性.这个属性的类型是List<Boolean>.SpEL会意识到list里的元素在被替换以前须要转化为布尔类型.简单例子以下:

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

simple simple=new Simple();

simple.booleanList.add(true);

StandardEvaluationContext simpleContext=new StandardEvaluationContext(simple);

//false这里是一个字符串;然SpEL须要在它插入集合以前转换为布尔类型

parser.parserExpression("booleanList[0]").setValue(simpleContext,"false");

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

10.3.2 Parser configuration 转化器配置

你可使用转化器配置对象(org.springframework.expression.spel.SpelParserConfiguration)来配置SpEL 表达式解析器.这个配置对象能够控制一些表达式组件的行为.例如,若是数据为索引指定的索引处元素是数组或集合的元素且是null;它能够自动地建立的元素.当用表达式来构建属性引用表达链会很是有用.若是指向数组或集合的下标且指定的下标在数组或集合当前容量以外,那么数组或集合会自增加以容纳这个下标.

lass 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表达式编译器来配置这个行为.

10.3.3 SpEL compilation

spring 4.1引入了基本属性编译器.表达式在转化中提供了达量的灵活性但不提供最佳性能.表达式用法通常很好,但被如spring Integration等组件使用时,虽然地位很重要,但对其活力没有真实的追求.

这个新的SpEL编译器倾向于表达这种需求.这个编译器会在表达式行为执行以前飞速产生一个真实的java类,并用该类实现更快的表达式转换.因为缺乏表达式类型,这个编译器会编译时翻译转化表达式时进行信息收集.固然,若是期间表达式元素的类型改变,这会给基于编译的信息带来困扰.所以,编译最适用于那种类型信息在重复替换中不会改变的表达式.

一个基本的表达式像这样:

someArray[0].someProperty.someotherProperty<0.1

这涉及数组访问,一些属性访问和数值运算,性能表现很是明显.在一个运行5千次迭代的维基准测试里,若是只是用解释器须要75毫秒转化值,而若是使用可编译的表达式则只需3毫秒.

Compile configuration 编译配置

编译器默认不会开启,可是有两种方式来开启它.它能够被先前提到的转化器配置开启或当SpEL在其余组件内使用能够经过系统属性开启.这节将讨论着两种方法.

很是重要来接力编译器操做的各类模式,能够在一个枚举里获取(org.springframework.expression.spel.spelCompilerMode),这些模式以下:

  • OFF 编译器关闭,这是默认的

  • IMMEDIATE (极速) 在极速模式下表达式会被尽量的编译.它通常在第一次解释器编译以后.若是编译表达式出错(通常都是类型变化),那么这个表达式转化的调用者会受到一个异常.

  • MIXED 混合模式下表达式在解释和编译模式之间悄悄转化.当一些解释器运行时,它户转化成编译模式.若是编译模式出了写错误,则自动转化为解释模式.有时他会产生其余形式的编译模式并转化为它.基本上用户在混合模式中遇到的异常会相应的进行替换处理.

极速模式之因此存在是由于混合模式产生的问题会对表达式产生负面影响.若是一个编译的表达式在部分红功以后,就会对系统状态形成影响.若是这样的话,调用者不想让它在解释模式下安静的重复运行,由于有些表达式可能会运行两次.

在选择一个模式,使用SpelParseConfiguration来配置这个转化器:

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

SpelExpressionParser parser=new SpelExpressinParser(config);

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

MyMessage message=new MyMessage();

Object payload = expr.getValue(message);

在指定编译模式时还能够指定一个类加载(也能够为空).编译的表达式会定义在提供的类加载器的子加载器下.保证同一个类加载器很是重要,由于它能够看到表达式转化过程当中的所欲类型.若是没有特定的,那么一个默认的类加载器就会使用(一般是当前线程表达式转化执行的上下文类加载器).

第二种方式配置编译器是当在其余组件内部使用SpEL时,你不可能经过一个配置对象来配置它.在这种状况下,可使用系统属性.spring.expression.compiler.mode属性能够设置为SpelCompilerMode枚举的一个值(off,immediate,mixed等).

Compiler limitations (编译器的局限)

spring 4.1以后基本的编译框架已经替换了.可是这个框架并不支持编译全部种类的表达式.出发点是通用的表达式在性能关键的上下文中使用.当前这些表达式的种类不会被编译:

  • expressions involving assignment 设计赋值的表达式
  • expressions relying on the conversion service 依赖转化服务的表达式
  • expression using custom resolvers or accessors 依赖自定义转化器或访问的表达式
  • expressions using selection or projection 使用筛选或投影的表达式

将来有更多类型的表达式会被编译

10.6 Classes used in the examples

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

}

10.4 Expresson support for defining bean definitions 表达式对bean定义的支持

SpEL表达式能够在xml或注解配置bean定义中使用.在这两种状况下多表达式都是#{<expression string>}的形式.

10.4.1 基于xml的配置

一个属性或构造器参数值可能以下表示:

<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'] }"/>

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

你能够经过名字指向其余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>

10.4.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 MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

10.5 Language Reference 语言引用

10.5.1 literal expression 字面值表达式

字面值表达式类型支持字符串,时间,数值(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()转化.

###10.5.2 Properties,Arrays,List,Maps,Indexers 属性,数组,集合,maps,索引器

执行属性引用很是容易:只需使用一个句号来标志一个内嵌属性.Inventor类的实例,pupin和tesla,能够在类中使用的例子中查看列表的数据操做.要向下指引,获得Tesla的出生年份和Pupin的出生地能够用之前用过的表达式.

//evals to 1956
int year=(Integer)parser.parseExpression("Birthdate.Year+1900").getValue(context);

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

属性名称的首字母大小写不明感.能够用方括号来得到数组和集合的内容.

ExpressionParser parser = new SpelExpressionParser();

//Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

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

//Members list
StandardEvaluationContext societyContext=new StandardEvaluationContext(ieee);

//evaluates to "Nikola Tesla"
String name=parser.parseExpression("Members[0].Name").getValue(societyContext,String.class);

//List and Array navigation
//evaluates to "Wireless communication"
String invention=parser.parserExpression("Members[0].Inventions[6]").getValue(societyContext,String.class);

maps里的内容能够被括号里的指定了字面值的key值访问.这种状况,适用于当前的名为Officers的map的key都是String类型的,全部咱们能够指定字符串字面量.

// 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");

10.5.3 Inline 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.parserExpression("{{'a','b'},{'v','d'}}").getValue(context);

{}意味着只是一个空集合.出于性能缘由,若是这个集合彻底有固定的字面值组成,那么会建立一个常量的list来表示这个表达式,而不是为每一个转化都建一个新的list.

10.5.4 Inline Maps 内嵌的maps

Maps能够被{key:value}的形式直接表示.

//evaluates to a Java map containing the two entries
Map inventorInfo =(Map)parser.parserExpression("{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自身由固定的文字或其余内嵌常量结构(lists or maps )组成,那么一个常量的map就会构建来表示这个表达式,而不是为每一次转化都建立一个新的map.map的key定义为引用类是可选的,上面的例子没有使用引用key.

10.5.5 Array construction (数组构造器)

数组可使用java语法建立,额外提供了一个初始器,能够在构造期间来操做这个数组.

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

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

//Multi dimensional array 多维数组
int[][] numbers3 = (int[][])parser.parserExpression(new int[4][5]).getValue(context);

目前对多维数组构造不提供初始器支持.

10.5.6 Methods 方法

方法能够被典型的java编程语法调用.你能够在字面值上调用方法.也支持变量.

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

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

10.5.7 operators 操做符

Relational operators 关系操做符

关系操做有:等于,不等于,小于,小于等于,大于,大于等于.这些都支持标准操做符号.

//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);

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

//evaluates to false
boolean falseValue=parse.parserExpression("'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})?$'");

注意:

要特别注意java的原生类,它们会被转化为包装类,因此依次预测 1 instanceof T(int) 判断是false,而1 instanceof T(Integer) 判断为true.

每个符号操做符能够指定为一个等效的字母.这能够避免在一些文档类型中有其余特定意义的符号,那么相应的表达式会没法使用(例如在一个xml文档中).文字替换符以下:lt(<),gt(>),le(<=),ge(>=),eq(==),ne(!=),div(/),mod(%),not(!).它们不区分大小写.

Logical operators 逻辑运算

逻辑运算支持且或非操做.它们的用法以下:

//--AND--

//evaluates to false
boolean falseValue=parser.parserExpression("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);

Mathematical operators 数学运算

加法运算能够在数字和字符串中使用.减法,乘法和除法运算只能用于数字.其余支持的数学运算是取余(%),指数运算(^).按运算符优先级使用.这些操做运算以下:

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

String testString=parser.parserExpression("'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);//6
double tweentyFour = 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

10.5.8 Assignment 赋值

参数的设置能够用指定操做完成.通常能够经过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);

10.5.9 Types 类

特殊的T操做符能够用来指定一个java.lang.Class的实例.这个操做符也能够调用静态方法.StandardEvaluationContext使用Typelocator来查找类,且StandardTypeLocator(可替代)创建在对java.lang包的理解上.这意味着在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);

能够用来调用静态方法和常量.

10.5.10 Contructors(构造器)

构造器可使用new操做符调用.除了原生类和String类外,须要使用类的全名称;

Inventor einstein = p.parserExpression("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.5.11 Variables 变量

变量在表达式中能够用#variableName来引用.变量能够经过StandardEvaluationContext中的setVariable方法设置.

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"

The #this and #root 变量

'#this' 变量用来定义和指向当前的处理对象(不合格的引用将会解决).变量#root一般定义和指向根级的上下文对象.#this通常经常使用语表达式组件的转化,而#root则指向上一层次.

//create an array of integers
List<Integer> primes=new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,4,5,7,11,13,17));

//create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context=new StandardEvaluationContext();
context.setVaribale("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);

10.5.12 Functions 函数

你能够经过注册用户定义的能够在表达式内调用的函数来扩展SpEl.这个函数可使用StandardEvaluationContext类的registerFunction()方法注册.

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 hellworldReversed=parser.parseExpression("#reverseString('hello world')").getValue(context,String.class);

10.5.13 Bean references bean引用

若是转换上下文配置了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);

要获得一个工厂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);

10.5.14 Ternary Operator(if-Then-Else) 三元操做符

你可使用三元运算符来进行表达式里的三元操做的条件逻辑判断.一个例子以下:

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

如上,这个布尔的返回结果是'falseExp'.一个更加复杂的例子以下:

parser.parseExpresson("Name").setValue(societyContext,"IEEE");
societyContext.setVariable("queryName","Nikola Tesla");

expression="isMember(#queryName)?#queryName+'is a member of the '+Name+' Society':#query+ '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操做符来查看三目运算符更简洁的语法.

10.5.15 The Elvis Operator 埃尔维斯算子

埃尔维斯算子是对三元操做的简化并能够在Groovy语言中使用.使用一个三元运算符,通常你要操做一个变量两次.例如:

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

埃尔维斯算子的命名来源于埃尔维斯的发型.name?:'Unkown';等同于 name=name!=null?name:"Unknown";

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的值
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Elvis Presley

10.5.16 Safe Navigation operator

安全导航操做来自于Groovy语言,主要为了不空指针异常.通常当你有一个对象的引用,你须要在访问该对象的方法和属性以前检查该对象是否为空.要避免这个,安全导航操做会简单的返回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 - does not throw NullPointerException!!!

注:埃尔维斯算子能够用来申请表达式中的默认值,例子:

@Value("#{systemProperties['pop3.port'] ?: 25}")

若是系统属性pop3.port为空,则定义25.

10.5.17 Collection Selection 集合筛选

筛选是一个强有力的表达式语言功能,容许你经过条目选择从一些资源集合转化到另外一些资源集合.

筛选使用语法.?[selectionExpression].它会拦截集合并返回一个包含了原始元素的子集的新集合.例如,筛选能够轻易得到集合中的叫Serbian的发明家.

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

筛选能够在list和map上适用.在这种形式下,筛选条件适用于每一个list中的个体.而map的匹配则是匹配操做Map中的每一个键值对(Map.Entry的对象).map的键值对访问同筛选中使用的属性同样.

这个表达式会返回一个新的有原来的map中的元素值都小于27的元素组成的新的map.

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

除了返回全部的元素以外,还能够返回第一个或最后一个值.要获得第一个匹配的值可使用^[...]的语法,获得最后一个则使用$[...]语法.

###10.5.18 Collection Projection 集合投影 投影容许一个集合驱动子表达式的转化,并产生一个新的集合.投影的语法以下:![projectionExpression].举个容易理解的例子,假设咱们有一个发明家的集合,但咱们想要一个他们想要出生城市的列表.最有效的是咱们获得发明家列表里的每一个实体的'placeOfBirth.city'.如此使用:

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

一个map也可使用投影,这种状况下,投影表达式能够转化map中每一个实体.map投影的结果是包含了map里全部键值中符合投影表达式的转化.

10.5.19 Expression templating 表达式模板

表达式模板容许字面文本和一个或多个转化模块的混合.每一个转化块能够用你定义的前缀和后缀来分割,一个通用的选择是使用#{}做为分隔符.例如:

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

// evaluates to "random number is 0.7038186818312008"

这个字符串是由字面值字符串'random number is' 和在#{}分隔符里表达式转化后合并的结果,分割符里的表达式调用了random()方法.parserExpression()方法的第二个参数的类型是ParserContext.这个ParserContext接口能够来改善表达式的转化以支持表达式模板功能.TeplateParserContext定义以下:

public class TemplateParserContext implements ParserContext{
	public String getExpressionPrefix(){
		return "#{";
	}
	public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}
相关文章
相关标签/搜索