Spring EL表达式原来都是配合这些类一起使用的-Spring专区论坛-技术-SpringForAll社区

Spring EL表达式原来都是配合这些类一起使用的

Spring EL表达式可以干什么

我们最常见的一些开源框架就经常会在注解中获取方法参数的值。

比如spring cache

    @GetMapping("/test")
    @Cacheable(cacheNames = "student", key = "#name")
    public  List<StudentVO> test(String name) {
        return mockSelectSql();
    }

这里的 #name就是EL表达式。代表获取入参name的值

今天我们就来探究下SpringEL表达式

Spring EL表达式的解析器 SpelExpressionParser

spring中实现EL表达式的解析器就是SpelExpressionParser这个类

使用也非常简单。我们通过下面的几个用例来了解一下

SpelExpressionParser parser = new SpelExpressionParser();
		String value = parser.parseExpression("'xiaozou'").getValue(String.class);//  ,注意string有单引号包裹
		Long value1 = parser.parseExpression("1.024E+3").getValue(Long.class);// 1024  , 指数形式
		Integer value2 = parser.parseExpression("0x208").getValue(Integer.class);// 520 十六进制  0x208
		Boolean value3 = parser.parseExpression("true").getValue(Boolean.class);// true
		Object value4 = parser.parseExpression("null").getValue();
		System.out.printf("value: %s\n value1: %s\n value2: %s\n value3: %s\n value4: %s\n", value, value1, value2, value3, value4);

对字符串和一些普通数字的解析好像没什么用。实际业务场景我也用不到。

对对象属性进行获取

SpelExpressionParser还可以获取对象的属性,比如向如下

ExpressionParser parser = new SpelExpressionParser();
		Expression expression = parser.parseExpression("name");
		Person person = new Person("xiaozou");
		String result = expression.getValue(person, String.class);
		System.out.println(result); // Output: xiaozou

方法调用

ExpressionParser parser = new SpelExpressionParser();
		Expression expression = parser.parseExpression("toUpperCase()");
		String str = "xiaozou";
		String result = expression.getValue(str, String.class);
		System.out.println(result); // Output: XIAOZOU

EL上下文 StandardEvaluationContext

当使用SpEL时,可以结合StandardEvaluationContext来设置根对象、变量和函数。

这样我们每次使用ExpressionParser就无需传入具体的对象,只从StandardEvaluationContext上下文中获取即可,实际spring中的一些EL表达式的实际使用都是结合StandardEvaluationContext使用的。

这里我们也来看一些使用用例

// 创建 SpelExpressionParser 实例
		SpelExpressionParser parser = new SpelExpressionParser();
		// 创建 StandardEvaluationContext 实例
		StandardEvaluationContext context = new StandardEvaluationContext();

		// 在上下文中设置变量
		context.setVariable("name", "John");
		context.setVariable("age", 30);

		// 解析表达式并计算结果
		Expression expression = parser.parseExpression("'Hello, ' + #name + '! You are ' + #age + ' years old.'");
		String result = expression.getValue(context, String.class);
        // 输出结果 Hello, xiaozou! You are 18 years old.
		System.out.println(result);

这种是直接放入常量,我们也可以放入对象

  ExpressionParser parser = new SpelExpressionParser();
		Expression expression = parser.parseExpression("name");
		Person person = new Person("xiao zou");
		StandardEvaluationContext context = new StandardEvaluationContext(person);
		String result = expression.getValue(context, String.class);
		System.out.println(result); // Output: xiaozou

Spring 参数解析器

从上面的很多个例子中我们可以看到Spring EL可以获取对象的属性,我们在解析对象的时候一般会要对象的变量名。

也是就SpringAOP中通过EL 表达式获取方法的参数的时候是需要参数名的,那么我们如何获取方法的参数名呢。

Spring提供了ParameterNameDiscoverer接口。 我们看看ParameterNameDiscoverer接口的实现类有哪些?

d2b5ca33bd20231115013743

可以看到有非常多,比较常用的就是LocalVariableTableParameterNameDiscovererDefaultParameterNameDiscoverer.

LocalVariableTableParameterNameDiscoverer是一种基于本地变量表的参数解析器,依赖于编译时的选项,可能无法在所有情况下获取参数名称。而DefaultParameterNameDiscoverer是一个更通用的参数解析器,尝试多种策略来获取参数名称,能够在更广泛的情况下获取到参数名称,但是效率相对会低一点。

一般使用LocalVariableTableParameterNameDiscoverer 即可

这里我以一个简单的例子来看看

@Test
	public void testParameterNameDiscoverer() throws Exception{
		ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
		Method method = MyClass.class.getMethod("myMethod", String.class, int.class);
		String[] parameterNames = discoverer.getParameterNames(method);
		for (String parameterName : parameterNames) {
			System.out.println("parameterName " + parameterName);// out  parameterName name parameterName age
		}
	}
	
	@Data
	static class MyClass {
		private String name = "Default";
		private int age = 0;
		public void myMethod(String name, int age) {
			// Method body
		}
	}

ExpressionParser+StandardEvaluationContext+ParameterNameDiscoverer

一般我们在使用Spring EL表达式的时候我们都会结合这三个类来使用。

比如我们写一个EL表达式的工具类

/**
     * el表达式解析
     *
     * @param expressionString 解析值
     * @param method           方法
     * @param args             参数
     * @return
     */
    public static Object parse(String expressionString, Method method, Object[] args) {
        if (DataUtils.isEmpty(expressionString)) {
            return null;
        }
        //获取被拦截方法参数名列表
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNameArr = discoverer.getParameterNames(method);
        //SPEL解析
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < Objects.requireNonNull(paramNameArr).length; i++) {
            context.setVariable(paramNameArr[i], args[i]);
        }
        return parser.parseExpression(expressionString).getValue(context);
    }

我们通过LocalVariableTableParameterNameDiscoverer解析方法的参数名字

然后将方法名和参数方法StandardEvaluationContext上下文中,然后通过EL解析器SpelExpressionParser去获取value

Spring cache 中EL表达式中的使用

实际Spring cache中也实现了自己的EL表达式解析

我们可以简单看看。核心实现类是CacheOperationExpressionEvaluator

d2b5ca33bd20231115013719CacheOperationExpressionEvaluator继承了一个抽象类CachedExpressionEvaluator

然后CachedExpressionEvaluator中就有具体的EL表达式的一些功能实现,包括我们之前提到的参数解析器ParameterNameDiscoverer,然后就是EL表达式解析的核心实现类SpelExpressionParserd2b5ca33bd20231115013657

其实我们可以看到EL表达式的组合拳三者已显其二,还有一个EvaluationContext在哪呢?

实际是在方法generateKey中去创建的。我们来看看源码

d2b5ca33bd20231115013643

图片[5]-Spring EL表达式原来都是配合这些类一起使用的-Spring专区论坛-技术-SpringForAll社区 可以看到在这里就创建了一个EvaluationContext,这里的上下文也是Cache模块自己实现一个类CacheEvaluationContext

具体放了个啥呢?实际就放了一个返回结果

d2b5ca33bd20231115013630

图片[7]-Spring EL表达式原来都是配合这些类一起使用的-Spring专区论坛-技术-SpringForAll社区

总结

本次我们详细的分析了Spring EL表达式中SpelExpressionParser的一些核心用法。以及实际使用中是如何结合ParameterNameDiscovererEvaluationContext去使用的。

简单实现了一个EL表达式的工具类,也结合Spring cache模块的EL表达式进行了简单的源码分析。

作者:小奏技术
链接:https://juejin.cn/post/7300779071389532214

请登录后发表评论

    没有回复内容