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

给你一囗甜甜゛ 提交于 2019-12-06 09:15:00

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

10.1 介绍

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

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

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

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

10.2 功能概览

有以下功能:

  • 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'.

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;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!