Spring SpEL表达式的使用
常见的应用场景:分布式锁的切面借助SpEL来构建key
比较另类的的应用场景:动态校验
个人感觉可以用作控制程序的走向,除此之外,spring的一些模块的自动配置类,也会在@Conditional注解中使用SpEL来实现有条件的加载特定的bean.
解释器设计模式的体现了
单纯的(非模板表达式)spel表达式将通过 SpelExpressionParser 创建 InternalSpelExpressionParser, 来实现 parseExpression() 的底层逻辑.
// org.springframework.expression.spel.standard.InternalSpelExpressionParser#doParseExpression
@Override
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
throws ParseException {
try {
this.expressionString = expressionString;
// 1.对相关的符号进行分词
Tokenizer tokenizer = new Tokenizer(expressionString);
this.tokenStream = tokenizer.process();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
// 2.构建 AST
SpelNodeImpl ast = eatExpression();
Assert.state(ast != null, "No node");
Token t = peekToken();
if (t != null) {
throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected");
// 3. 返回创建好的表达式实例
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
throw ex.getCause();
}
}
TemplateParserContext
使用了#{
、 }
ParserContext.isTemplate()
来决定处理流程 // org.springframework.expression.common.TemplateAwareExpressionParser#parseExpression(java.lang.String, org.springframework.expression.ParserContext)
@Override
public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
if (context != null && context.isTemplate()) {
return parseTemplate(expressionString, context);
}
else {
return doParseExpression(expressionString, context);
}
}
转换并获取表达式对应的数值
因为要debug beanFactory关联的 parser,便懒得构造applicationContext,直接@SpringbootTest 启动容器了
@DisplayName("Spring Expression Language")
@SpringBootTest
public class SpELTest {
@Value("#{testBean.value}")
String value;
@Autowired
ApplicationContext appCtx;
SpelExpressionParser parser;
StandardEvaluationContext stdEvaCtx;
@PostConstruct
private void postConstruct() throws NoSuchMethodException {
parser = new SpelExpressionParser();
// rootObject
stdEvaCtx = new StandardEvaluationContext(new TestBean("rootValue", null));
// variable
stdEvaCtx.setVariable("testBean", this.appCtx.getBean("testBean"));
Method parseInt = Integer.class.getDeclaredMethod("valueOf", String.class);
stdEvaCtx.registerFunction("doValueOf", parseInt);
stdEvaCtx.setBeanResolver(new BeanFactoryResolver(this.appCtx));
}
@DisplayName("注解方式")
@Test
void t0() {
// 这个上下文其实就是表达式、变量的容器(缓存)
System.err.println(this.value);
}
@DisplayName("编码方式")
@Test
void t1() {
// 不需要 {}
// spring security 中也使用编码的方式解析权限注解 @PrePreAuthorize
Expression expression = parser.parseExpression("#testBean.value");
System.err.println(expression.getValue(this.stdEvaCtx));
}
@DisplayName("运算")
@Nested
class Count {
@DisplayName("字面量")
@Test
void t0() {
// 上下文中找不到这个变量,报错:
// spel.SpelEvaluationException: EL1007E: Property or field 'test' cannot be found on null
// System.err.println("找不到变量="+parser.parseExpression("test").getValue(String.class));
// 字符串
System.err.println("字符串1=" + parser.parseExpression("'test'").getValue(String.class));
System.err.println("字符串2=" + parser.parseExpression("\"test\"").getValue(String.class));
// 数字
System.err.println("int=" + parser.parseExpression("1").getValue(Integer.class));
System.err.println("long=" + parser.parseExpression("1L").getValue(long.class));
System.err.println("float=" + parser.parseExpression("1.1").getValue(Float.class));
System.err.println("double=" + parser.parseExpression("1.1E+1").getValue(double.class));
System.err.println("hex=" + parser.parseExpression("0xf").getValue(int.class));
// 布尔
System.err.println("bool=" + parser.parseExpression("false").getValue(boolean.class));
// null
System.err.println(parser.parseExpression("null").getValue());
}
@DisplayName("算数")
@Test
void t1() {
System.err.println("3+2=" + parser.parseExpression("3+2").getValue(Integer.class));
System.err.println("3-2=" + parser.parseExpression("3-2").getValue(Integer.class));
System.err.println("3*2=" + parser.parseExpression("3*2").getValue(Integer.class));
System.err.println("3/2=" + parser.parseExpression("3/2").getValue(Integer.class));
System.err.println("3%2=" + parser.parseExpression("3%2").getValue(Integer.class));
System.err.println("3^2=" + parser.parseExpression("3^2").getValue(Integer.class));
}
@DisplayName("关系运算")
@Test
void t2() {
System.err.println("3==2=" + parser.parseExpression("3==2").getValue(Boolean.class));
System.err.println("3 == 2=" + parser.parseExpression("3 == 2").getValue(Boolean.class));
System.err.println("3 ge 2 =" + parser.parseExpression("3 ge 2").getValue(boolean.class));
System.err.println("3 LT 2 = " + parser.parseExpression("3 LT 2").getValue(boolean.class));
// SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'NE2'
// System.err.println("3NE2 = "+parser.parseExpression("3NE2").getValue(boolean.class));
// 并不能返回 int:0、1,会抛出异常
System.err.println("2 between {1, 2}=" + parser.parseExpression("2 between {1, 2}").getValue(Boolean.class));
// SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'le(<=)'
// System.err.println("1<2<=2="+parser.parseExpression("1<2<=3").getValue(Boolean.class));
}
@DisplayName("逻辑运算")
@Test
void t3() {
System.err.println("2>1 and false = " + parser.parseExpression("2>1 and false").getValue(boolean.class));
System.err.println("2>1 && false = " + parser.parseExpression("2>1 && false").getValue(Boolean.class));
// SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'factory_bean_ref(&)'
// System.err.println("2>1 & false = "+parser.parseExpression("2>1 & false").getValue(Boolean.class));
System.err.println("2>1 or NOT false and (! NOT false || 1==1) = " + parser.parseExpression("2>1 or NOT false and (! NOT false || 1==1)").getValue(Boolean.class));
}
@DisplayName("三目运算")
@Test
void t4() {
System.err.println("3 > 2 ? true : false = " + parser.parseExpression("3 > 2 ? true : false").getValue(boolean.class));
}
@DisplayName("elivis")
@Test
void t5() {
System.err.println("null ?: 'false' = " + parser.parseExpression("null ?: 'false'").getValue(Boolean.class));
System.err.println("null ?: 'false' = " + parser.parseExpression("null ?: 'false'").getValue(String.class));
}
@DisplayName("正则")
@Test
void t6() {
System.err.println("" + parser.parseExpression("'123' matches '\\d{3}'").getValue(boolean.class));
System.err.println("" + parser.parseExpression("123 matches '\\d{3}'").getValue(Boolean.class));
}
@DisplayName("instanceof")
@Test
void t7() {
System.err.println("'123' instanceof T(String) = " + parser.parseExpression("'123' instanceof T(String)").getValue(Boolean.class));
System.err.println("123 instanceof T(String) = " + parser.parseExpression("123 instanceof T(java.lang.String)").getValue(Boolean.class));
}
}
@DisplayName("类型")
@Nested
class Type {
@DisplayName("class")
@Test
void t0() {
// java.lang 以外的类均需要全限定名
System.err.println(parser.parseExpression("T(String)").getValue(Class.class));
System.err.println(parser.parseExpression("T(java.util.Map)").getValue(Class.class));
// 访问 静态的属性、方法
System.err.println(parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class));
System.err.println(parser.parseExpression("T(Integer).parseInt(3)").getValue(Integer.class));
}
@DisplayName("instance")
@Test
void t1() {
// java.lang 包 同理
System.err.println(parser.parseExpression("new String('苹果一样的甜')").getValue(String.class));
System.err.println(parser.parseExpression("new java.util.Date()").getValue(Date.class));
}
@DisplayName("reference")
@Test
void t2() {
System.err.println("#testBean.value=" + parser.parseExpression("#testBean.value").getValue(stdEvaCtx, String.class));
System.err.println("#this.value=" + parser.parseExpression("#this.value").getValue(stdEvaCtx, String.class));
System.err.println("#root.value=" + parser.parseExpression("#root.value").getValue(stdEvaCtx, String.class));
// rootObject缺省时,访问其属性,不能加#前缀
System.err.println("(root属性可以省略#root)value=" + parser.parseExpression("value").getValue(stdEvaCtx, String.class));
}
@DisplayName("assign")
@Test
void t3() {
System.err.println("#testBean.value=newValue --> " + parser.parseExpression("#testBean.value='newValue'").getValue(stdEvaCtx, String.class));
System.err.println("#this.value=newThisValue --> " + parser.parseExpression("#this.value='newThisValue'").getValue(stdEvaCtx, String.class));
System.err.println("#root.value=newRootValue --> " + parser.parseExpression("#root.value='newRootValue'").getValue(stdEvaCtx, String.class));
System.err.println("value=newValue --> " + parser.parseExpression("value='newValue'").getValue(stdEvaCtx, String.class));
}
@DisplayName("func")
@Test
void t4() {
System.err.println(parser.parseExpression("#doValueOf('20').byteValue()").getValue(stdEvaCtx, Byte.class));
}
@DisplayName("对象属性获取 及 安全导航")
@Test
void t5() {
System.err.println(parser.parseExpression("value").getValue(stdEvaCtx, String.class));
// Value 可以,Value 不得行(首字母不敏感)
System.err.println(parser.parseExpression("Value").getValue(stdEvaCtx, String.class));
// 支持groovy的elivis表达式
// 安全导航运算符前面的#root可以省略,但后面的#root不可省略
System.err.println(parser.parseExpression("#root?.#root").getValue(stdEvaCtx, TestBean.class));
System.err.println(parser.parseExpression("value?.#root.value").getValue(stdEvaCtx, String.class));
// SpelParseException: Expression [username?.'核弹拉链'] @8: EL1049E: Unexpected data after '.': ''核弹拉链''
// SpEL引入了Groovy语言中的安全导航运算符"(对象|属性)?.属性"
// 常量显然不得行
// System.err.println(parser.parseExpression("username?.'核弹拉链'").getValue(stdEvaCtx, String.class));
}
@DisplayName("对象方法调用")
@Test
void t6() {
System.err.println(parser.parseExpression("value.substring(1, 6).toUpperCase()").getValue(stdEvaCtx, String.class));
System.err.println(parser.parseExpression("toString()").getValue(stdEvaCtx, String.class));
}
@DisplayName("bean引用(BeanFactoryResolver)")
@Test
void t7() {
// @BeanName
// EvaluationContext.setBeanResolver() 需要借助 beanFactory
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("@systemProperties").getValue(stdEvaCtx, Properties.class)));
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("@testBean").getValue(stdEvaCtx, TestBean.class)));
}
}
@DisplayName("集合")
@Nested
class Collect {
@DisplayName("内联数组")
@Test
void t0() {
System.err.println(Arrays.toString(parser.parseExpression("new int[2]{1, 2}").getValue(int[].class)));
System.err.println(Arrays.toString(parser.parseExpression("new String[2][2]").getValue(String[][].class)));
// 不支持多维数组创建同时,做初始化
System.err.println(Arrays.toString(parser.parseExpression("new String[2][2]{'1','2'},{'3','4'}").getValue(String[][].class)));
}
@DisplayName("内联集合")
@Test
void t1() {
System.err.println(parser.parseExpression("{}").getValue(List.class));
// java.util.Collections$UnmodifiableRandomAccessList
System.err.println(parser.parseExpression("{1, 2,3}").getValue(List.class).getClass().getName());
// 此时的 List .class = java.util.ArrayList
// 存在非字面量表达式时,集合类型将转为原始类型(可修改的集合)
System.err.println(parser.parseExpression("{{1+2,2+4},{3,4+4}}").getValue(List.class).getClass().getName());
}
@DisplayName("数组、集合、字典元素访问")
@Test
void t2() {
System.err.println(parser.parseExpression("[0]").getValue(new int[]{1, 2, 3}, Integer.class));
System.err.println(parser.parseExpression("{1, 2, 3}[0]").getValue(int.class));
System.err.println(parser.parseExpression("[0]").getValue(Lists.newArrayList(1, 2, 3), int.class));
Map<String, Integer> map = Maps.newHashMap();
map.put("weng", 4);
map.put("chong", 5);
map.put("yu", 2);
System.err.println(parser.parseExpression("['chong']").getValue(map, int.class));
}
// 很像 streamApi.peek().collect(toList())
@DisplayName("数组、集合、字典 转换")
@Test
void t3() {
// array|list|map.![表达式]
System.err.println(Arrays.toString(parser.parseExpression("#root.![#this+1]").getValue(new int[]{1, 2, 3}, int[].class)));
System.err.println(parser.parseExpression("#root.![#this+1]").getValue(Lists.newArrayList(1, 2, 3), List.class));
Map<String, Integer> map = Maps.newHashMap();
map.put("weng", 4);
map.put("chong", 5);
map.put("yu", 2);
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.![#this.key+1]").getValue(map, List.class)));
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.![#this.value+1]").getValue(map, List.class)));
// 报错: cannot convert from java.util.ArrayList to java.util.Map
// 集合、数组之间可以随意转换
// System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.![#this.value+1]").getValue(map, Map.class)));
}
// 相当于 streamApi.filter.collect(toList)
@DisplayName("数组、集合、字典 选择")
@Test
void t4() {
// array|list|map.?[表达式]
System.err.println(Arrays.toString(parser.parseExpression("#root.?[#this>=2]").getValue(new int[]{1, 2, 3}, int[].class)));
System.err.println(Arrays.toString(parser.parseExpression("#root.?[#this>=2]").getValue(Lists.newArrayList(1, 2, 3), int[].class)));
Map<String, Integer> map = Maps.newHashMap();
map.put("weng", 4);
map.put("chong", 5);
map.put("yu", 2);
// {"weng":4,"yu":2}
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.?[#this.key!='chong']").getValue(map, Map.class)));
// 这里转的集合,有些怪异
// [{"weng":4,"yu":2}]
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.?[#this.key!='chong']").getValue(map, List.class)));
System.err.println(Jsons.NO_OP.stringify(parser.parseExpression("#root.?[#this.value<=2]").getValue(map, Map.class)));
}
}
}
附上相关的类
@Component("testBean")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TestBean {
@Value("${angel.spel.key}")
public String value;
private String username;
}