Files
2023-06-29 20:16:26 +08:00

22 KiB
Raw Permalink Blame History

Spel

Spel表达式计算

示例

如下是一个Spel表达式计算示例

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();
// 最终message计算的值是'Hello World'

Concept

ExpressionParser接口用于对表达式字符串进行解析,在上述示例中,表达式字符串是一个由单引号包围起来的字符串字面量。

Expression接口则是负责对表达式字符串进行计算。

在调用parser.parseExpressionexp.getValue,分别会抛出ParseExceptionEvaluationException异常。

Spel支持许多特性例如调用方法访问属性调用构造方法等示例如下

调用方法

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

调用属性

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 
int length = (Integer) exp.getValue();

调用构造器

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)方法向getValue方法传递一个Class对象可以避免在调用getValue方法调用之后需要将返回值手动转化为特定类型。如果类型转化失败会抛出一个EvaluationException异常。

在特定对象上计算表达式

Spel支持在一个特定的对象上该对象通常被称作root object进行计算例如如下示例

// 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中放置一个变量并且在后续中使用该变量

// 向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包含内置的类型转换并且可以为类型之间指定自定义的类型转换。该转换是支持泛型的。

如下为支持泛型的示例:

class Simple {
	public List<Boolean> 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对象的域、方法参数、构造器参数指定默认值。通过如下方式来指定表达式:

  • #{ <expression string> }.

为field指定默认值

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指定默认值

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注解为参数指定默认值

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

	// ...
}

Spel语法

字面量表达式

字面量表达式支持字符串、数值整数、实数、十六进制数、boolean类型和null。

字符串类型字面量

其中字符串类型的字面量通过单引号包围,如果想要将单引号本身放入字符串字面量中,可以使用两个单引号:

// 该值为h''h
parser.parseExpression("'h''''h'").getValue()

字面量的使用如下所示:

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

访问属性

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


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

属性名的第一个字符不区分大小写。

Array

可以通过方括号来访问array和list中的元素内容

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来访问示例如下所示

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

列表表示

列表可以直接通过如下方式来表示:

// 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语法

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中的语法

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

操作符

关系操作符

关系操作符示例如下:

// 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<null永远为false

instanceof操作符和matches操作符

// evaluates to false
boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").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})?$'").getValue(Boolean.class);

逻辑操作符

逻辑操作符支持and、or、not

// -- AND --

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

数学运算符

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

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

double twentyFour = 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

赋值运算符

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操作符会获取一个class对象在传入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操作符来调用构造方法在调用构造方法需要指定全类名但是基类和String可以不指定全类名

Inventor einstein =
  p.parseExpression("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);
                                                        

变量

可以通过#argName语法来引用变量,可以通过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"

#this and #root

#this变量一直都是被定义的用于引用当前评估对象#root变量也是一直都被定义的引用root context对象。

// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
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<Integer> primesGreaterThanTen =
             (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);

方法注册

可以向StandardEvaluationContext中注册方法注册的方法可以在spel中使用

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中

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对象

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

三元操作符

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(),?:表达式可以达成同样效果:

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中的类似

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.Entryentry的key和value都可以作为属性访问。

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

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

集合映射

类似于Stream.mapspel支持集合映射操作符

// returns [ 'Smiljan', 'Idvor' ]
// 将Inventor集合映射为city集合
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

表达式模板

表达式模板允许将字面量文本和一个或多个评估component混合在一起评估块语法类似于${}

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

// evaluates to "random number is 0.7038186818312008"