本文纯属spring官方文档翻译版,详情请参考:Core Technologies
Spring 表达式语言(简称“SpEL”),Spring Expression Language
-
主要功能
-
方法调用
-
字符串模板
-
-
一般使用场景
-
@Value
-
类似于OGNL(对象图导航语言,Object-Graph Navigation Language)
-
mybatis XML中使用的表达式
-
-
String函数调用
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); String message = (String) exp.getValue();
以下调用 JavaBean 属性的示例调用了该String
属性Bytes
:
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes()' Expression exp = parser.parseExpression("'Hello World'.bytes"); byte[] bytes = (byte[]) exp.getValue();
标准点表示法
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes().length' Expression exp = parser.parseExpression("'Hello World'.bytes.length"); int length = (Integer) exp.getValue();
调用 String 的构造函数
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); String message = exp.getValue(String.class);
SpEL 更常见的用法是提供针对特定对象实例(称为根对象)进行评估的表达式字符串。以下示例显示如何name
从Inventor
类的实例中检索属性或创建布尔条件:
// Create and set a calendar GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); // The constructor arguments are name, birthday, and nationality. Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("name"); // Parse name as an expression String name = (String) exp.getValue(tesla); // name == "Nikola Tesla" exp = parser.parseExpression("name == 'Nikola Tesla'"); boolean result = exp.getValue(tesla, Boolean.class); // result == true
理解EvaluationContext
EvaluationContext
在评估表达式以解析属性、方法或字段并帮助执行类型转换时,使用该接口。Spring 提供了两种实现。
-
SimpleEvaluationContext
:针对不需要完整范围的 SpEL 语言语法且应受到有意义限制的表达式类别,公开了基本 SpEL 语言功能和配置选项的子集。示例包括但不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开全套 SpEL 语言功能和配置选项。您可以使用它来指定默认根对象并配置每个可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用、构造函数和 bean 引用。它还要求您明确选择对表达式中的属性和方法的支持级别。默认情况下,create()
静态工厂方法只允许对属性进行读取访问。您还可以获得构建器来配置所需的确切支持级别,针对以下一项或某种组合:
-
PropertyAccessor
仅自定义(无反射) -
只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SpEL 使用 Spring 核心 ( org.springframework.core.convert.ConversionService
) 中可用的转换服务。此转换服务带有许多用于常见转换的内置转换器,但也是完全可扩展的,因此您可以在类型之间添加自定义转换。此外,它是泛型感知的。这意味着,当您在表达式中使用泛型类型时,SpEL 会尝试转换以维护它遇到的任何对象的类型正确性。
这在实践中意味着什么?假设使用 赋值setValue()
来设置List
属性。属性的类型实际上是List<Boolean>
. SpEL 认识到列表中的元素Boolean
在放入之前需要转换为。以下示例显示了如何执行此操作:
class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } Simple simple = new Simple(); simple.booleanList.add(true); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); // "false" is passed in here as a String. SpEL and the conversion service // will recognize that it needs to be a Boolean and convert it accordingly. parser.parseExpression("booleanList[0]").setValue(context, simple, "false"); // b is false Boolean b = simple.booleanList.get(0);
解析器配置
可以使用解析器配置对象 ( org.springframework.expression.spel.SpelParserConfiguration
)来配置 SpEL 表达式解析器。配置对象控制一些表达式组件的行为。例如,如果您对数组或集合进行索引,并且指定索引处的元素是null
,SpEL可以自动创建元素。这在使用由属性引用链组成的表达式时很有用。如果您对数组或列表进行索引并指定超出数组或列表当前大小末尾的索引,SpEL 可以自动增长数组或列表以适应该索引。为了在指定索引处添加元素,SpEL 将在设置指定值之前尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认构造函数,null
将被添加到数组或列表中。如果没有知道如何设置值的内置或自定义转换器,null
则将保留在数组或列表中指定索引处。以下示例演示了如何自动增长列表:
class Demo { public List<String> list; } // Turn on: // - auto null reference initialization // - auto collection growing SpelParserConfiguration config = new SpelParserConfiguration(true, true); ExpressionParser parser = new SpelExpressionParser(config); Expression expression = parser.parseExpression("list[3]"); Demo demo = new Demo(); Object o = expression.getValue(demo); // demo.list will now be a real collection of 4 entries // Each entry is a new empty String
SpEL 编译
Spring Framework 4.1 包含一个基本的表达式编译器。通常会解释表达式,这在评估期间提供了很大的动态灵活性,但不能提供最佳性能。对于偶尔的表达式使用,这很好,但是,当被其他组件(例如 Spring Integration)使用时,性能可能非常重要,并且不需要动态。 SpEL 编译器旨在满足这一需求。在评估期间,编译器生成一个体现表达式运行时行为的 Java 类,并使用该类来实现更快的表达式评估。由于没有围绕表达式键入,编译器在执行编译时使用在表达式的解释评估期间收集的信息。例如,它不能仅从表达式中知道属性引用的类型,但在第一次解释评估期间,它会找出它是什么。当然,如果各种表达式元素的类型随着时间的推移而变化,那么基于这些派生信息进行编译可能会在以后造成麻烦。出于这个原因,编译最适合那些类型信息不会在重复计算中改变的表达式。 考虑以下基本表达式:
omeArray[0].someProperty.someOtherProperty < 0.1
因为前面的表达式涉及数组访问、一些属性取消引用和数值运算,所以性能提升可能非常明显。在 50000 次迭代的示例微基准测试中,使用解释器评估需要 75 毫秒,而使用表达式的编译版本只需要 3 毫秒。
编译器配置
编译器默认不打开,但您可以通过两种不同的方式之一打开它。您可以通过使用解析器配置过程(前面讨论过)或在 SpEL 使用嵌入到另一个组件中时使用 Spring 属性来打开它。本节讨论这两个选项。
编译器可以在org.springframework.expression.spel.SpelCompilerMode
枚举中捕获的三种模式之一中运行 。模式如下:
-
OFF
(默认):编译器关闭。 -
IMMEDIATE
: 在立即模式下,表达式会尽快编译。这通常是在第一次解释评估之后。如果编译表达式失败(通常是由于类型更改,如前所述),则表达式求值的调用者会收到异常。 -
MIXED
:在混合模式下,表达式会随着时间在解释模式和编译模式之间静默切换。在经过一定次数的解释运行后,它们会切换到编译形式,如果编译形式出现问题(例如类型更改,如前所述),表达式会自动再次切换回解释形式。一段时间后,它可能会生成另一个已编译的表单并切换到它。基本上,用户进入IMMEDIATE
模式的异常是在内部处理的。
IMMEDIATE
mode 存在是因为MIXED
mode 可能会导致具有副作用的表达式出现问题。如果编译的表达式在部分成功后崩溃,它可能已经做了一些影响系统状态的事情。如果发生这种情况,调用者可能不希望它在解释模式下静默重新运行,因为部分表达式可能会运行两次。
选择模式后,使用SpelParserConfiguration
配置解析器。以下示例显示了如何执行此操作:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader()); SpelExpressionParser parser = new SpelExpressionParser(config); Expression expr = parser.parseExpression("payload"); MyMessage message = new MyMessage(); Object payload = expr.getValue(message);
当你指定编译模式时,你也可以指定一个类加载器(允许传递null)。已编译的表达式在任何提供的子类加载器下创建的子类加载器中定义。重要的是要确保,如果指定了类加载器,它可以看到表达式评估过程中涉及的所有类型。如果您不指定类加载器,则使用默认类加载器(通常是在表达式评估期间运行的线程的上下文类加载器)。
配置编译器的第二种方法是在 SpEL 嵌入到某个其他组件中时使用,并且可能无法通过配置对象对其进行配置。在这些情况下,也能够设定spring.expression.compiler.mode
通过JVM系统属性(或通过属性 SpringProperties机构)的一个 SpelCompilerMode
枚举值(off
,immediate
,或mixed
)。
编译器限制
从 Spring Framework 4.1 开始,基本的编译框架就到位了。但是,该框架还不支持编译所有类型的表达式。最初的重点是可能在性能关键的上下文中使用的常用表达式。目前无法编译以下类型的表达式: 涉及赋值的表达式 依赖于转换服务的表达式 使用自定义解析器或访问器的表达式 使用选择或投影的表达式 将来会编译更多类型的表达式。
Bean 定义中的表达式
您可以将 SpEL 表达式与基于 XML 或基于注释的配置元数据一起使用来定义BeanDefinition实例。在这两种情况下,定义表达式的语法都是#{ <expression string> }.
XML 配置
可以使用表达式设置属性或构造函数参数值,如以下示例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean>
应用程序上下文中的所有 bean 都可以作为具有公共 bean 名称的预定义变量使用。这包括用于访问运行时环境的标准上下文 bean,例如environment
(of type org.springframework.core.env.Environment
) 以及systemProperties
and systemEnvironment
(of type Map<String, Object>
)。
以下示例显示了对systemProperties
作为 SpEL 变量的bean 的访问:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <!-- other properties --> </bean>
请注意,您不必在#
此处使用符号作为预定义变量的前缀。
您还可以按名称引用其他 bean 属性,如以下示例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean> <bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> <!-- other properties --> </bean>
注释配置
要指定默认值,您可以将@Value
注释放在字段、方法以及方法或构造函数参数上。
以下示例设置字段的默认值:
public class FieldValueTestBean { @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
以下示例显示了等效的但在属性设置器方法上:
public class PropertyValueTestBean { private String defaultLocale; @Value("#{ systemProperties['user.region'] }") public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
自动装配的方法和构造函数也可以使用@Value
注解,如以下示例所示:
public class SimpleMovieLister { private MovieFinder movieFinder; private String defaultLocale; @Autowired public void configure(MovieFinder movieFinder, @Value("#{ systemProperties['user.region'] }") String defaultLocale) { this.movieFinder = movieFinder; this.defaultLocale = defaultLocale; } // ... }
public class MovieRecommender { private String defaultLocale; private CustomerPreferenceDao customerPreferenceDao; public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; this.defaultLocale = defaultLocale; } // ... }
语言参考
表达式语言支持以下功能:
-
文字表达
-
布尔和关系运算符
-
常用表达
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系运算符
-
任务
-
调用构造函数
-
Bean 引用
-
数组构造
-
内联列表
-
内联地图
-
三元运算符
-
变量
-
用户自定义函数
-
收藏投影
-
收藏选择
-
模板化表达式
每个符号运算符也可以指定为纯字母等效项。这避免了使用的符号对于嵌入表达式的文档类型(例如在 XML 文档中)具有特殊含义的问题。文本等价物是:
-
lt
(<
) -
gt
(>
) -
le
(<=
) -
ge
(>=
) -
eq
(==
) -
ne
(!=
) -
div
(/
) -
mod
(%
) -
not
(!
)。
所有文本运算符都不区分大小写。
逻辑运算符
SpEL 支持以下逻辑运算符:
-
and
(&&
) -
or
(||
) -
not
(!
)
数学运算符
您可以+
对数字和字符串使用加法运算符 ( )。您只能对数字使用减法 ( -
)、乘法 ( *
) 和除法 ( /
) 运算符。您还可以对数字使用模数 ( %
) 和指数幂 ( ^
) 运算符。强制执行标准运算符优先级
赋值运算符
要设置属性,请使用赋值运算符 ( =
)
类型
您可以使用特殊T
运算符来指定java.lang.Class
(类型)的实例。静态方法也可以使用此运算符调用。在 StandardEvaluationContext
使用TypeLocator
查找类型以及 StandardTypeLocator
(可替换)是建立与所述的理解 java.lang
包。这意味着T()
对java.lang
包中类型的引用不需要完全限定,但所有其他类型引用必须是完全限定的。以下示例显示了如何使用T
运算符:
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); boolean trueValue = parser.parseExpression( "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean.class);
在#this
和#root
变量
该#this
变量始终被定义并引用当前的评估对象(针对哪些不合格的引用被解析)。该#root
变量始终被定义并引用根上下文对象。尽管#this
评估表达式的组件时可能会有所不同,但#root
始终指的是根。以下示例显示了如何使用#this
和#root
变量:
职能
您可以通过注册可以在表达式字符串中调用的用户定义函数来扩展 SpEL。该功能通过EvaluationContext
. 以下示例显示了如何注册用户定义的函数:
Method method = ...; EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method);
public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(input.length()); for (int i = 0; i < input.length(); i++) { backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } }
可以注册并使用上述方法,如以下示例所示:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class);
Bean 引用
如果评估上下文已经配置了 bean 解析器,您可以使用@
符号从表达式中查找 bean 。以下示例显示了如何执行此操作:
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@something").getValue(context);
要访问工厂 bean 本身,您应该在 bean 名称前加上一个&
符号。以下示例显示了如何执行此操作:
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation Object bean = parser.parseExpression("&foo").getValue(context);
三元运算符(If-Then-Else)
您可以使用三元运算符在表达式内执行 if-then-else 条件逻辑。以下清单显示了一个最小示例:
String falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Elvis 运算符
ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class); System.out.println(name); // 'Unknown'
安全导航操作员
安全导航运算符用于避免 aNullPointerException
和来自Groovy 语言。通常,当您有一个对象的引用时,您可能需要在访问该对象的方法或属性之前验证它不为空。为避免这种情况,安全导航运算符返回 null 而不是抛出异常。以下示例显示了如何使用安全导航运算符:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); System.out.println(city); // null - does not throw NullPointerException!!!
集合选择
选择是一种强大的表达式语言功能,可让您通过从其条目中进行选择将源集合转换为另一个集合。
选择使用.?[selectionExpression]
. 它过滤集合并返回一个包含原始元素子集的新集合。例如,选择可以让我们轻松获得塞尔维亚发明人的列表,如下例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression( "members.?[nationality == 'Serbian']").getValue(societyContext);
集合投影
投影让集合驱动子表达式的评估,结果是一个新的集合。投影的语法是.![projectionExpression]
. 例如,假设我们有一个发明者列表,但想要他们出生的城市列表。实际上,我们希望为发明者列表中的每个条目评估“placeOfBirth.city”。以下示例使用投影来执行此操作:
// returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
数组和任何实现java.lang.Iterable
或 的 东西都支持投影java.util.Map
。当使用地图来驱动投影时,投影表达式会根据地图中的每个条目(表示为 Java Map.Entry
)进行评估。跨地图投影的结果是一个列表,其中包含对每个地图条目的投影表达式的评估
表达式模板
表达式模板允许将文字文本与一个或多个评估块混合。每个评估块都由您可以定义的前缀和后缀字符分隔。一个常见的选择是#{ }
用作分隔符,如以下示例所示:
tring 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()
方法的结果)连接来评估字符串。该parseExpression()
方法的第二个参数是类型ParserContext
。该ParserContext
接口用于影响表达式的解析方式,以支持表达式模板功能。的定义TemplateParserContext
如下:
public class TemplateParserContext implements ParserContext { public String getExpressionPrefix() { return "#{"; } public String getExpressionSuffix() { return "}"; } public boolean isTemplate() { return true; } }
示例中使用的类
package org.spring.samples.spel.inventor; import java.util.Date; import java.util.GregorianCalendar; public class Inventor { private String name; private String nationality; private String[] inventions; private Date birthdate; private PlaceOfBirth placeOfBirth; public Inventor(String name, String nationality) { GregorianCalendar c= new GregorianCalendar(); this.name = name; this.nationality = nationality; this.birthdate = c.getTime(); } public Inventor(String name, Date birthdate, String nationality) { this.name = name; this.nationality = nationality; this.birthdate = birthdate; } public Inventor() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNationality() { return nationality; } public void setNationality(String nationality) { this.nationality = nationality; } public Date getBirthdate() { return birthdate; } public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } public PlaceOfBirth getPlaceOfBirth() { return placeOfBirth; } public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { this.placeOfBirth = placeOfBirth; } public void setInventions(String[] inventions) { this.inventions = inventions; } public String[] getInventions() { return inventions; } }
package org.spring.samples.spel.inventor; public class PlaceOfBirth { private String city; private String country; public PlaceOfBirth(String city) { this.city=city; } public PlaceOfBirth(String city, String country) { this(city); this.country = country; } public String getCity() { return city; } public void setCity(String s) { this.city = s; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } }
package org.spring.samples.spel.inventor; import java.util.*; public class Society { private String name; public static String Advisors = "advisors"; public static String President = "president"; private List<Inventor> members = new ArrayList<Inventor>(); private Map officers = new HashMap(); public List getMembers() { return members; } public Map getOfficers() { return officers; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isMember(String name) { for (Inventor inventor : members) { if (inventor.getName().equals(name)) { return true; } } return false; } }