From 85227de47df26c8a9c3b904db6fc8ac3640bcc97 Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Wed, 3 Jan 2024 23:05:21 +0800 Subject: [PATCH] =?UTF-8?q?ExpressionParser=E6=BA=90=E7=A0=81=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- pom.xml | 1 + .../README.md | 19 +- .../README.md | 2 +- spring-spel/pom.xml | 19 ++ .../spring-spel-expressionParser/README.md | 196 ++++++++++++++++++ .../spring-spel-expressionParser/pom.xml | 14 ++ .../com/xcs/spring/ExpressionParserDemo.java | 39 ++++ 8 files changed, 282 insertions(+), 14 deletions(-) create mode 100644 spring-spel/pom.xml create mode 100644 spring-spel/spring-spel-expressionParser/README.md create mode 100644 spring-spel/spring-spel-expressionParser/pom.xml create mode 100644 spring-spel/spring-spel-expressionParser/src/main/java/com/xcs/spring/ExpressionParserDemo.java diff --git a/README.md b/README.md index f08b77e..4548017 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,8 @@ - Spring 表达式语言(SpEL) - - `ExpressionParser`: 解析字符串形式的 SpEL 表达式,创建并返回 Expression 实例。 - - - `Expression`: 代表解析后的 SpEL 表达式,提供求值、赋值等功能。 - + - [`ExpressionParser`](spring-spel/spring-spel-expressionParser/README.md): 解析字符串形式的 SpEL 表达式,创建并返回 Expression 实例。 + - `EvaluationContext`: 定义用于 SpEL 表达式求值的环境,包括变量、根对象等。 - `PropertyAccessor`: 处理属性的读写操作,支持表达式中的属性访问。 diff --git a/pom.xml b/pom.xml index cbeaf74..efda4c2 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ spring-factory spring-env spring-dataops + spring-spel diff --git a/spring-annotation/spring-annotation-propertySource/README.md b/spring-annotation/spring-annotation-propertySource/README.md index 6bb58a6..f6e3477 100644 --- a/spring-annotation/spring-annotation-propertySource/README.md +++ b/spring-annotation/spring-annotation-propertySource/README.md @@ -1,4 +1,5 @@ ## @PropertySource + - [@PropertySource](#propertysource) - [一、基本信息](#一基本信息) - [二、注解描述](#二注解描述) @@ -480,23 +481,23 @@ private void addPropertySource(PropertySource propertySource) { ### 八、注意事项 1. **文件位置** - + 确保我们提供的文件路径是正确的。例如,`classpath:` 前缀表示文件应该在类路径中,而 `file:` 前缀则表示文件应该在文件系统的特定位置。 + + 确保你提供的文件路径是正确的。例如,`classpath:` 前缀表示文件应该在类路径中,而 `file:` 前缀则表示文件应该在文件系统的特定位置。 2. **占位符** - + 在 `@PropertySource` 的 `value` 属性中,我们可以使用 `${...}` 占位符,它们将会被已注册的任何属性源解析。 + + 在 `@PropertySource` 的 `value` 属性中,你可以使用 `${...}` 占位符,它们将会被已注册的任何属性源解析。 3. **处理重复的属性源名称** - + 如果我们有多个 `@PropertySource` 注解(或使用 `@PropertySources` 注解)且它们具有相同的名称,那么它们会合并。后声明的 `@PropertySource` 将覆盖先前声明的同名 `@PropertySource`。 + + 如果你有多个 `@PropertySource` 注解(或使用 `@PropertySources` 注解)且它们具有相同的名称,那么它们会合并。后声明的 `@PropertySource` 将覆盖先前声明的同名 `@PropertySource`。 4. **属性源的顺序** - + 属性源的顺序很重要,因为在多个属性源中定义的同名属性将使用先找到的值。我们可以使用 `PropertySource` 的 `name` 属性来明确指定属性源的名称,以控制其在环境中的顺序。 + + 属性源的顺序很重要,因为在多个属性源中定义的同名属性将使用先找到的值。你可以使用 `PropertySource` 的 `name` 属性来明确指定属性源的名称,以控制其在环境中的顺序。 5. **忽略找不到的资源** - + 我们可以使用 `ignoreResourceNotFound` 属性来指定当属性文件找不到时是否应该抛出异常。默认情况下,这是 `false`,意味着如果属性文件找不到,会抛出异常。设置为 `true` 可以让Spring在找不到文件时安静地继续运行。 + + 你可以使用 `ignoreResourceNotFound` 属性来指定当属性文件找不到时是否应该抛出异常。默认情况下,这是 `false`,意味着如果属性文件找不到,会抛出异常。设置为 `true` 可以让Spring在找不到文件时安静地继续运行。 6. **字符编码** - + 从Spring 4.3开始,`@PropertySource` 注解有一个 `encoding` 属性,允许我们为给定的资源指定特定的字符编码。 + + 从Spring 4.3开始,`@PropertySource` 注解有一个 `encoding` 属性,允许你为给定的资源指定特定的字符编码。 7. **自定义属性源工厂** - + 如果我们需要特殊的逻辑来创建属性源,可以使用 `factory` 属性来指定一个自定义的 `PropertySourceFactory`。 + + 如果你需要特殊的逻辑来创建属性源,可以使用 `factory` 属性来指定一个自定义的 `PropertySourceFactory`。 8. **激活属性占位符解析** - + 仅仅使用 `@PropertySource` 并不会激活属性占位符解析。为了替换我们的bean定义中的 `${...}` 占位符,我们还需要添加 `@Bean` 定义为 `PropertySourcesPlaceholderConfigurer`。 + + 仅仅使用 `@PropertySource` 并不会激活属性占位符解析。为了替换你的bean定义中的 `${...}` 占位符,你还需要添加 `@Bean` 定义为 `PropertySourcesPlaceholderConfigurer`。 9. **与Profiles结合** - + 我们可以与Spring的Profile功能结合使用 `@PropertySource`,以根据不同的环境加载不同的属性文件。 + + 你可以与Spring的Profile功能结合使用 `@PropertySource`,以根据不同的环境加载不同的属性文件。 ### 九、总结 diff --git a/spring-aware/spring-aware-beanClassLoaderAware/README.md b/spring-aware/spring-aware-beanClassLoaderAware/README.md index a8081da..9b184d9 100644 --- a/spring-aware/spring-aware-beanClassLoaderAware/README.md +++ b/spring-aware/spring-aware-beanClassLoaderAware/README.md @@ -16,7 +16,7 @@ ### 一、基本信息 -✒️ **作者** - Lex 📝 **博客** - [我的CSDN]() 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [BeanClassLoaderAware源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-aware/spring-aware-beanClassLoaderAware) +✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133916700) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [BeanClassLoaderAware源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-aware/spring-aware-beanClassLoaderAware) ### 二、接口描述 diff --git a/spring-spel/pom.xml b/spring-spel/pom.xml new file mode 100644 index 0000000..002c6f3 --- /dev/null +++ b/spring-spel/pom.xml @@ -0,0 +1,19 @@ + + + + spring-reading + com.xcs.spring + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel + pom + + spring-spel-expressionParser + spring-spel-expression + + + \ No newline at end of file diff --git a/spring-spel/spring-spel-expressionParser/README.md b/spring-spel/spring-spel-expressionParser/README.md new file mode 100644 index 0000000..e3f0e0a --- /dev/null +++ b/spring-spel/spring-spel-expressionParser/README.md @@ -0,0 +1,196 @@ +## ExpressionParser + +- [ExpressionParser](#expressionparser) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + + +### 一、基本信息 + +✒ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **XML 和/或注解配置** + + + 熟悉使用 XML 或注解来配置 Spring 应用程序,因为 SpEL 常用于这些配置文件中。 + +2. **理解 SpEL 语法** + + + SpEL 有自己的语法规则。了解这些规则对于编写有效的 SpEL 表达式至关重要。 + +3. **了解 AOP 和事件处理** + + + 虽然不是必需的,但了解 Spring 中的 AOP 和事件处理机制对于理解 SpEL 在复杂场景下的应用是有益的。 + +### 三、基本描述 + +`ExpressionParser` 接口是 Spring 框架中的一个关键组件,它专门用于解析和执行 Spring 表达式语言(SpEL)的表达式。SpEL 是一种功能丰富的表达式语言,允许在运行时动态地操作和查询对象图。通过 `ExpressionParser` 接口,我们可以将字符串形式的表达式转换为 `Expression` 对象,并对这些对象执行各种操作,例如获取值、设置值或执行复杂的表达式。 + +### 四、主要功能 + +1. **解析表达式** + + + 它可以解析基于文本的表达式字符串,将其转换为 `Expression` 对象。这是它的核心功能,允许将动态表达式嵌入到应用程序中。 + +2. **表达式求值** + + + 通过解析得到的 `Expression` 对象,可以对表达式进行求值,以获取其运行时值。这可能涉及访问对象属性、调用方法、使用逻辑运算符等。 + +3. **设置表达式值** + + + 除了获取表达式的值,`ExpressionParser` 还可以用来修改目标对象的属性。通过表达式,可以直接设置对象的某个属性值。 + +4. **类型转换** + + + 它还支持类型转换功能,能夠在表达式求值过程中自动将值从一种类型转换为另一种类型。 + +5. **函数和运算符处理** + + + `ExpressionParser` 支持使用各种函数和运算符,例如数学运算、字符串操作、逻辑运算等。 + +6. **集合操作** + + + 支持对集合和数组的操作,包括选择(selection)、投影(projection)和聚合(aggregation)等。 + +7. **条件表达式** + + + 能够处理条件表达式,例如 if-then-else 结构,提供了增强的决策能力。 + +8. **模板解析** + + + 可以处理带有模板文本的表达式,例如拼接字符串与动态表达式的结合。 + +### 五、接口源码 + +`ExpressionParser` 接口主要负责将表达式字符串转换为可评估的 `Expression` 对象。它支持解析标准表达式和模板,提供了两种方法来解析表达式:一种是基本解析,另一种允许通过上下文进行更灵活的解析。 + +```java +/** + * 解析表达式字符串,编译成可评估的表达式。 + * 支持解析标准表达式字符串以及模板。 + * + * @author Keith Donald + * @author Andy Clement + * @since 3.0 + */ +public interface ExpressionParser { + + /** + * 解析表达式字符串并返回一个可用于重复评估的 Expression 对象。 + *

一些例子: + *

+	 *     3 + 4
+	 *     name.firstName
+	 * 
+ * @param expressionString 需要解析的原始表达式字符串 + * @return 已解析表达式的评估器 + * @throws ParseException 解析过程中发生的异常 + */ + Expression parseExpression(String expressionString) throws ParseException; + + /** + * 解析表达式字符串并返回一个可用于重复评估的 Expression 对象。 + *

一些例子: + *

+	 *     3 + 4
+	 *     name.firstName
+	 * 
+ * @param expressionString 需要解析的原始表达式字符串 + * @param context 影响此表达式解析过程的上下文(可选) + * @return 已解析表达式的评估器 + * @throws ParseException 解析过程中发生的异常 + */ + Expression parseExpression(String expressionString, ParserContext context) throws ParseException; + +} +``` + +### 六、主要实现 + +1. **SpelExpressionParser** + + + 这是最常用的实现,提供了对 Spring 表达式语言(SpEL)的完整支持。它能够处理各种复杂的表达式,如方法调用、属性访问、关系运算符等。 + +### 七、最佳实践 + +使用`SpelExpressionParser` 来解析和评估表达式。首先,创建了一个 `SpelExpressionParser` 实例来解析表达式。接着,代码分别处理了两种情况:一是解析并评估一个简单的数学表达式 "100 * 2 + 10",二是在一个包含上下文变量(`myVariable` 设为 50)的表达式 "100 * #myVariable + 10" 中进行解析和评估。每个表达式的结果都被计算并打印出来。 + +```java +public class ExpressionParserDemo { + + public static void main(String[] args) { + // 创建解析器实例 + ExpressionParser parser = new SpelExpressionParser(); + + // 解析基本表达式 + try { + Expression expression = parser.parseExpression("100 * 2 + 10"); + Integer result = expression.getValue(Integer.class); + System.out.println("表达式 '100 * 2 + 10' 的结果为: " + result); + } catch (ParseException e) { + System.err.println("解析表达式出错: " + e.getMessage()); + } + + // 使用上下文的解析 + try { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariable("myVariable", 50); + Expression expressionWithContext = parser.parseExpression("100 * #myVariable + 10"); + Integer resultWithContext = expressionWithContext.getValue(context, Integer.class); + System.out.println("带上下文的表达式 '100 * #myVariable + 10' 的结果为: " + resultWithContext); + } catch (ParseException e) { + System.err.println("解析带上下文的表达式出错: " + e.getMessage()); + } + } +} +``` + +运行结果发现,`SpelExpressionParser` 如何能够处理包括数学运算和变量替换在内的复杂表达式,并准确地计算出结果。 + +```java +表达式 '100 * 2 + 10' 的结果为: 210 +带上下文的表达式 '100 * #myVariable + 10' 的结果为: 5010 +``` + +### 八、与其他组件的关系 + +1. **Expression** + + + 解析表达式字符串时,`SpelExpressionParser` 返回实现了 `Expression` 接口的对象。这些对象代表解析后的表达式,可以用于获取表达式的值、设置值或执行表达式。 + +2. **EvaluationContext** + + + 在评估表达式时,可以使用实现 `EvaluationContext` 接口的对象,如 `StandardEvaluationContext`,来提供表达式运行时的上下文信息(例如变量定义)。这有助于增强表达式的动态性和灵活性。 + +3. **ParseException&EvaluationException** + + + 这些是处理表达式解析和评估时可能出现的异常的类。如果在解析或评估表达式时出现错误,将抛出这些异常。 + +4. **PropertyAccessor&MethodResolver** + + + 这些类和接口用于在评估表达式时解析对象属性和方法调用。它们允许 `SpelExpressionParser` 与 Java 对象的属性和方法交互,实现复杂的逻辑。 + +5. **TypeConverter** + + + 用于在表达式计算过程中进行类型转换,使得可以在不同类型之间自由转换值。 + +### 九、常见问题 + +1. **表达式语法错误** + + 编写 SpEL 表达式时,常见的错误包括拼写错误、错误的符号或操作符使用。这些错误通常会在解析表达式时抛出 `ParseException`。 +2. **性能问题** + + 频繁解析和评估复杂的 SpEL 表达式可能会影响应用性能。合理缓存解析后的表达式对象可以帮助缓解这一问题。 +3. **上下文变量未找到** + + 如果在表达式中使用了上下文(Context)中未定义的变量,将会抛出异常。确保所有在表达式中使用的变量都已在上下文中定义。 +4. **类型转换问题** + + 在表达式求值过程中,可能会出现类型不匹配或不能正确转换的情况,导致 `EvaluationException`。 +5. **属性或方法访问问题** + + 尝试访问不存在的属性或调用不存在的方法时,会抛出异常。这可能是由于拼写错误或对象类型不正确。 \ No newline at end of file diff --git a/spring-spel/spring-spel-expressionParser/pom.xml b/spring-spel/spring-spel-expressionParser/pom.xml new file mode 100644 index 0000000..b8f4f34 --- /dev/null +++ b/spring-spel/spring-spel-expressionParser/pom.xml @@ -0,0 +1,14 @@ + + + + spring-spel + com.xcs.spring + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel-expressionParser + + \ No newline at end of file diff --git a/spring-spel/spring-spel-expressionParser/src/main/java/com/xcs/spring/ExpressionParserDemo.java b/spring-spel/spring-spel-expressionParser/src/main/java/com/xcs/spring/ExpressionParserDemo.java new file mode 100644 index 0000000..89899ca --- /dev/null +++ b/spring-spel/spring-spel-expressionParser/src/main/java/com/xcs/spring/ExpressionParserDemo.java @@ -0,0 +1,39 @@ +package com.xcs.spring; + +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * @author xcs + * @date 2024年1月3日22:35:51 + */ +public class ExpressionParserDemo { + + public static void main(String[] args) { + // 创建解析器实例 + ExpressionParser parser = new SpelExpressionParser(); + + // 解析基本表达式 + try { + Expression expression = parser.parseExpression("100 * 2 + 10"); + Integer result = expression.getValue(Integer.class); + System.out.println("表达式 '100 * 2 + 10' 的结果为: " + result); + } catch (ParseException e) { + System.err.println("解析表达式出错: " + e.getMessage()); + } + + // 使用上下文的解析 + try { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariable("myVariable", 50); + Expression expressionWithContext = parser.parseExpression("100 * #myVariable + 10"); + Integer resultWithContext = expressionWithContext.getValue(context, Integer.class); + System.out.println("带上下文的表达式 '100 * #myVariable + 10' 的结果为: " + resultWithContext); + } catch (ParseException e) { + System.err.println("解析带上下文的表达式出错: " + e.getMessage()); + } + } +}