- [Spel](#spel) - [Spel表达式计算](#spel表达式计算) - [示例](#示例) - [Concept](#concept) - [调用方法](#调用方法) - [调用属性](#调用属性) - [调用构造器](#调用构造器) - [在特定对象上计算表达式](#在特定对象上计算表达式) - [EvaluationContext](#evaluationcontext) - [EvaluationContext实现类](#evaluationcontext实现类) - [TypeConversion](#typeconversion) - [在定义Bean对象时使用Spel表达式](#在定义bean对象时使用spel表达式) - [为field指定默认值](#为field指定默认值) - [通过setter为field指定默认值](#通过setter为field指定默认值) - [@Autowired方法和构造器](#autowired方法和构造器) - [Spel语法](#spel语法) - [字面量表达式](#字面量表达式) - [字符串类型字面量](#字符串类型字面量) - [Properties,Array,List,Map,Indexer](#propertiesarraylistmapindexer) - [访问属性](#访问属性) - [Array](#array) - [Map](#map) - [列表表示](#列表表示) - [数组构建](#数组构建) - [方法调用](#方法调用) - [操作符](#操作符) - [关系操作符](#关系操作符) - [instanceof操作符和matches操作符](#instanceof操作符和matches操作符) - [逻辑操作符](#逻辑操作符) - [数学运算符](#数学运算符) - [赋值运算符](#赋值运算符) - [类型操作符](#类型操作符) - [构造方法](#构造方法) - [变量](#变量) - [#this and #root](#this-and-root) - [方法注册](#方法注册) - [引用bean对象](#引用bean对象) - [三元操作符](#三元操作符) - [Elvis Operator](#elvis-operator) - [空安全操作符](#空安全操作符) - [集合过滤操作符](#集合过滤操作符) - [集合映射](#集合映射) - [表达式模板](#表达式模板) # Spel ## Spel表达式计算 ### 示例 如下是一个Spel表达式计算示例: ```java ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); String message = (String) exp.getValue(); // 最终message计算的值是'Hello World' ``` ### Concept `ExpressionParser`接口用于对表达式字符串进行解析,在上述示例中,表达式字符串是一个由单引号包围起来的字符串字面量。 `Expression`接口则是负责对表达式字符串进行计算。 在调用`parser.parseExpression`和`exp.getValue`,分别会抛出`ParseException`和`EvaluationException`异常。 Spel支持许多特性,例如调用方法,访问属性,调用构造方法等,示例如下: #### 调用方法 ```java ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); String message = (String) exp.getValue(); // message值为Hello World! ``` #### 调用属性 ```java ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes().length' Expression exp = parser.parseExpression("'Hello World'.bytes.length"); int length = (Integer) exp.getValue(); ``` #### 调用构造器 ```java ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); String message = exp.getValue(String.class); ``` 通过`public T getValue(Class desiredResultType)`方法,向getValue方法传递一个Class对象,可以避免在调用getValue方法调用之后需要将返回值手动转化为特定类型。如果类型转化失败,会抛出一个EvaluationException异常。 #### 在特定对象上计算表达式 Spel支持在一个特定的对象上(该对象通常被称作root object)进行计算,例如如下示例: ```java // 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 EvalutaionContext用于给表达式提供一个执行的上下文环境,通过EvalutaionContext,可以向context中放置一个变量,并且在后续中使用该变量: ```java // 向context中放入一个list变量 ctx.setVariable("list",list) // 在后续表达式中即可使用放入的list变量 // 获取放入context中的list变量的值 parser.parseExpression("#list[0]").getValue(ctx); // 设置context中list变量的值 parser.parseExpression("#list[0]").setValue(ctx , "false"); ``` #### EvaluationContext实现类 - SimpleEvaluationContext:实现了Spel语言的部分特性 - StandardEvaluationContext:实现了Spel语言的全部特性 ### TypeConversion 默认情况下,spel会使用`org.springframework.core.convert.ConversionService`中的Conversion Service。该conversion service包含内置的类型转换,并且可以为类型之间指定自定义的类型转换。该转换是支持泛型的。 如下为支持泛型的示例: ```java class Simple { public List booleanList = new ArrayList<>(); } 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); ``` ### 在定义Bean对象时使用Spel表达式 可以通过@Value注解来为bean对象的域、方法参数、构造器参数指定默认值。通过如下方式来指定表达式: - `#{ }.` #### 为field指定默认值 ```java public class FieldValueTestBean { @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } } ``` #### 通过setter为field指定默认值 ```java public class PropertyValueTestBean { private String defaultLocale; @Value("#{ systemProperties['user.region'] }") public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } } ``` #### @Autowired方法和构造器 @Autowired方法和构造器可以使用@Value注解为参数指定默认值: ```java 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; } // ... } ``` ```java 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; } // ... } ``` ## Spel语法 ### 字面量表达式 字面量表达式支持字符串、数值(整数、实数、十六进制数)、boolean类型和null。 #### 字符串类型字面量 其中字符串类型的字面量通过单引号包围,如果想要将单引号本身放入字符串字面量中,可以使用两个单引号: ```java // 该值为h''h parser.parseExpression("'h''''h'").getValue() ``` 字面量的使用如下所示: ```java 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(); ``` ### Properties,Array,List,Map,Indexer #### 访问属性 ```java int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); ``` 属性名的第一个字符不区分大小写。 #### Array 可以通过方括号来访问array和list中的元素内容: ```java 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.parseExpression("Members[0].Inventions[6]").getValue(societyContext,String.class); ``` #### Map map中的值可以在方括号中指定字面量key来访问,示例如下所示: ```java // 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"); ``` #### 列表表示 列表可以直接通过如下方式来表示: ```java // evaluates to a Java list containing the four numbers List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); ``` #### 数组构建 spel表达式中数组构建可以使用java语法: ```java int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); // Array with initializer int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); // Multi dimensional array int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); ``` ### 方法调用 表达式中方法调用也可以使用java中的语法: ```java // string literal, evaluates to "bc" String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class); // evaluates to true boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class); ``` ### 操作符 #### 关系操作符 关系操作符示例如下: ```java // 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); ``` 与null比较时的规则: - 任何值都比null大,`X>null`永远为true - 没有任何值比null小,`X primes = new ArrayList(); primes.addAll(Arrays.asList(2,3,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.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] List primesGreaterThanTen = (List) parser.parseExpression("#primes.?[#this>10]").getValue(context); ``` ### 方法注册 可以向StandardEvaluationContext中注册方法,注册的方法可以在spel中使用: ```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(); } } ``` 注册方法到context中: ```java 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对象 如果为context配置了bean resolver,可以通过`@`语法来查询bean对象: ```java 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); ``` ### 三元操作符 ```java 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 Operator 类似于`Optional.ofNullable(var).orElse()`,`?:`表达式可以达成同样效果: ```java 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 ``` ### 空安全操作符 `?.`操作符和js中的类似: ```java 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!!! ``` ### 集合过滤操作符 `?[selectionExpression]`可以完成集合的过滤,该操作符可以同时针对list和map,在针对map操作时,操作的是`Map.Entry`,entry的key和value都可以作为属性访问。 ```java // 针对list List list = (List) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext); // 针对map Map newMap = parser.parseExpression("map.?[value<27]").getValue(); ``` ### 集合映射 类似于Stream.map,spel支持集合映射操作符 ```java // returns [ 'Smiljan', 'Idvor' ] // 将Inventor集合映射为city集合 List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]"); ``` ### 表达式模板 表达式模板允许将字面量文本和一个或多个评估component混合在一起,评估块语法类似于`${}` ```java String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // evaluates to "random number is 0.7038186818312008" ```