spring.expression 随笔0 概述

0. 我只是个普通码农,不值得挽留

Spring SpEL表达式的使用
常见的应用场景:分布式锁的切面借助SpEL来构建key
比较另类的的应用场景:动态校验


个人感觉可以用作控制程序的走向,除此之外,spring的一些模块的自动配置类,也会在@Conditional注解中使用SpEL来实现有条件的加载特定的bean.

1. UML

1.1 ExpressionParser

解释器设计模式的体现了
在这里插入图片描述
单纯的(非模板表达式)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();
		}
	}

1.2 ParserContext

在这里插入图片描述

  • 这里前后缀例如:支持模板表达式的实现类TemplateParserContext使用了#{}
  • TemplateAwareExpressionParser(支持模板的ExpressionParser实现类),根据 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);
		}
	}

1.3 Expression

在这里插入图片描述

转换并获取表达式对应的数值

1.4 EvaluateContext

  • 支持间接的关联 beanFactory ,来注入spring bean
  • 该功能很好的体现了 spring-expression 的独立性
  • 支持往该上下文中加入静态方法、java bean
  • 与 Expression.getValue() 有较大的关系
    在这里插入图片描述

在这里插入图片描述

2. test-classes

因为要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<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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肯尼思布赖恩埃德蒙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值