From 660bbbbd9b2c0f81bcee61cb19e15f06bd5746ba Mon Sep 17 00:00:00 2001 From: linlei Date: Tue, 12 Mar 2024 17:58:09 +0800 Subject: [PATCH] =?UTF-8?q?ConstructorResolver=E6=BA=90=E7=A0=81=E5=88=86?= =?UTF-8?q?=E6=9E=90=20MethodResolver=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= =?UTF-8?q?=20BeanResolver=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90=20TypeLocat?= =?UTF-8?q?or=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +- spring-spel/pom.xml | 4 + .../spring-spel-beanResolver/README.md | 186 ++++++++ spring-spel/spring-spel-beanResolver/pom.xml | 15 + .../java/com/xcs/spring/BeanResolverDemo.java | 30 ++ .../src/main/java/com/xcs/spring/MyBean.java | 4 + .../spring-spel-constructorResolver/README.md | 256 +++++++++++ .../spring-spel-constructorResolver/pom.xml | 14 + .../xcs/spring/ConstructorResolverDemo.java | 22 + .../src/main/java/com/xcs/spring/MyBean.java | 25 + .../com/xcs/spring/EvaluationContextDemo.java | 4 + .../spring-spel-methodResolver/README.md | 426 ++++++++++++++++++ .../spring-spel-methodResolver/pom.xml | 14 + .../com/xcs/spring/MethodResolverDemo.java | 33 ++ .../src/main/java/com/xcs/spring/MyBean.java | 7 + .../spring-spel-propertyAccessor/pom.xml | 8 +- spring-spel/spring-spel-typeLocator/README.md | 229 ++++++++++ spring-spel/spring-spel-typeLocator/pom.xml | 14 + .../java/com/xcs/spring/TypeLocatorDemo.java | 23 + 19 files changed, 1325 insertions(+), 9 deletions(-) create mode 100644 spring-spel/spring-spel-beanResolver/README.md create mode 100644 spring-spel/spring-spel-beanResolver/pom.xml create mode 100644 spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/BeanResolverDemo.java create mode 100644 spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/MyBean.java create mode 100644 spring-spel/spring-spel-constructorResolver/README.md create mode 100644 spring-spel/spring-spel-constructorResolver/pom.xml create mode 100644 spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/ConstructorResolverDemo.java create mode 100644 spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/MyBean.java create mode 100644 spring-spel/spring-spel-methodResolver/README.md create mode 100644 spring-spel/spring-spel-methodResolver/pom.xml create mode 100644 spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MethodResolverDemo.java create mode 100644 spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MyBean.java create mode 100644 spring-spel/spring-spel-typeLocator/README.md create mode 100644 spring-spel/spring-spel-typeLocator/pom.xml create mode 100644 spring-spel/spring-spel-typeLocator/src/main/java/com/xcs/spring/TypeLocatorDemo.java diff --git a/README.md b/README.md index 7797eb6..25bbcf0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ - [`AnnotationMetadata`](spring-metadata/spring-metadata-annotationMetadata/README.md):动态获取和操作运行时类注解信息。 - [`TypeFilter`](spring-metadata/spring-metadata-typeFilter/README.md):组件扫描时自定义类筛选,支持复杂条件和精确过滤。 - [`Condition`](spring-metadata/spring-metadata-condition/README.md):条件判断,决定Bean创建和配置的灵活机制。 + + 验证、数据绑定和类型转换 - [`Validator`](spring-dataops/spring-dataops-validator/README.md):提供自定义数据验证逻辑,确保模型对象满足业务规则。 - [`PropertyEditor`](spring-dataops/spring-dataops-propertyEditor/README.md):自定义JavaBean属性的转换逻辑,处理属性类型转换。 @@ -82,11 +83,16 @@ - [`Parser`](spring-dataops/spring-dataops-parser/README.md):用于将文本解析为对象,专注于解析逻辑。 - `BeanWrapper`:用于操作JavaBean的属性,实现动态属性设置和获取。 - `AnnotationFormatterFactory`:针对带注解字段的格式化器工厂,链接注解与格式化逻辑。 + + Spring 表达式语言(SpEL) + - [`ExpressionParser`](spring-spel/spring-spel-expressionParser/README.md): 解析字符串形式的 SpEL 表达式,创建并返回 Expression 实例。 - [`EvaluationContext`](spring-spel/spring-spel-evaluationContext/README.md): 管理SpEL表达式的上下文信息 - [`PropertyAccessor`](spring-spel/spring-spel-propertyAccessor/README.md): 用于读取和写入对象的属性,可用于实现自定义的属性访问逻辑 - - [`ExpressionParser`](spring-spel/spring-spel-expressionParser/README.md): 解析字符串形式的 SpEL 表达式,创建并返回 Expression 实例。 - + - [`ConstructorResolver`](spring-spel/spring-spel-constructorResolver/README.md): 解析构造函数以实例化bean + - [`MethodResolver`](spring-spel/spring-spel-methodResolver/README.md): 解析方法以调用对象的方法 + - [`BeanResolver`](spring-spel/spring-spel-beanResolver/README.md): 解析Bean以获取其实例 + - [`TypeLocator`](spring-spel/spring-spel-typeLocator/README.md): 定位并获取类型信息 + + Bean工厂 - [`BeanFactory`](spring-factory/spring-factory-beanFactory/README.md):Spring的核心接口,提供对Bean的配置、创建、管理的基本功能。 - [`ListableBeanFactory`](spring-factory/spring-factory-listableBeanFactory/README.md):支持按类型获取Bean的集合。 @@ -94,29 +100,35 @@ - [`ConfigurableBeanFactory`](spring-factory/spring-factory-configurableBeanFactory/README.md):提供对BeanFactory配置的扩展,如属性编辑器、作用域等。 + [`AutowireCapableBeanFactory`](spring-factory/spring-factory-autowireCapableBeanFactory/README.md):Bean创建、初始化、注入、销毁的核心功能接口。 + [`ConfigurableListableBeanFactory`](spring-factory/spring-factory-configurableListableBeanFactory/README.md):支持配置和列表操作的可配置Bean工厂接口。 + + 容器上下文 - [`ClassPathXmlApplicationContext`](spring-context/spring-context-classPathXmlApplicationContext/README.md):类路径(classpath)加载 XML 配置文件的上下文。 - [`AnnotationConfigApplicationContext`](spring-context/spring-context-annotationConfigApplicationContext/README.md):注解配置类中加载配置信息的上下文。 - `GenericApplicationContext`:支持多种配置方式,XML、注解、手动注册的上下文。 + + Bean定义与注册 - [`BeanDefinition`](spring-beans/spring-bean-beanDefinition/README.md):详细描述Bean,支持依赖注入、AOP、作用域控制等核心功能。 - [`BeanDefinitionHolder`](spring-beans/spring-bean-beanDefinitionHolder/README.md):管理和操作BeanDefinition的关键类。 - [`BeanDefinitionRegistry`](spring-beans/spring-bean-beanDefinitionRegistry/README.md):Bean定义注册管理关键接口,处理Bean元数据。 + + Bean定义读取与扫描 - [`XmlBeanDefinitionReader`](spring-beans/spring-bean-xmlBeanDefinitionReader/README.md):加载解析XML配置,构建IOC容器,注册Bean定义。 - [`PropertiesBeanDefinitionReader`](spring-beans/spring-bean-propertiesBeanDefinitionReader/README.md):属性文件加载,解析为Bean定义。 - [`GroovyBeanDefinitionReader`](spring-beans/spring-bean-groovyBeanDefinitionReader/README.md):Groovy脚本解析为Bean定义。 - [`AnnotatedBeanDefinitionReader`](spring-beans/spring-bean-annotatedBeanDefinitionReader/README.md):注解配置,自动扫描注册Spring组件,简化Bean定义配置。 - [`ClassPathBeanDefinitionScanner`](spring-beans/spring-bean-classPathBeanDefinitionScanner/README.md):类路径扫描注册Spring Bean,支持自动装配。 + + Bean定义导入与组合 - `ImportBeanDefinitionRegistrar`:运行时动态注册 Bean,实现灵活配置,扩展配置类功能。 - `ImportSelector`:运行时动态导入配置类,实现条件选择和灵活配置。 - `DeferredImportSelector`:运行时动态导入配置,支持条件选择和按组别延迟加载。 + + Bean生命周期 - [`Bean的定义注册过程`](spring-core/spring-core-registerBeanDefinition):加载与解析配置文件,注册解析Bean定义,类名、作用域、属性等。 - [`Bean的初始化过程`](spring-core/spring-core-getBean/README.md):实例化、属性注入、Aware回调、后置处理器、初始化方法调用。 - [`Bean的依赖解析过程`](spring-core/spring-core-resolveDependency/README.md):声明依赖,查找依赖,注入依赖,处理循环依赖,延迟依赖解析。 - [`Bean的销毁过程`](spring-core/spring-core-destroyBean/README.md):销毁方法调用,接口回调,后处理清理,通知触发,GC回收资源。 + + 属性解析和环境配置 - [`PropertySource`](spring-env/spring-env-propertySource/README.md):管理各种配置源的抽象类,支持灵活地加载和访问应用配置。 - [`PropertySources`](spring-env/spring-env-propertySources/README.md):用于统一管理和访问多个 PropertySource 实例,简化配置数据的处理。 @@ -124,6 +136,7 @@ - [`ConfigurablePropertyResolver`](spring-env/spring-env-configurablePropertyResolver/README.md):属性解析配置,占位符设置,适应不同配置需求。 - [`Environment`](spring-env/spring-env-environment/README.md):应用环境表示,提供属性访问,支持配置文件,实现动态配置。 - [`ConfigurableEnvironment`](spring-env/spring-env-configurableEnvironment/README.md):动态配置应用环境,激活、默认配置,提升应用灵活性。 + + Bean初始化与扩展点 - [`InitializingBean`](spring-interface/spring-interface-initializingBean/README.md):提供Bean初始化时执行自定义逻辑的接口。 - [`DisposableBean`](spring-interface/spring-interface-disposableBean/README.md):定义Bean销毁前执行清理操作的接口。 @@ -135,6 +148,7 @@ - [`MergedBeanDefinitionPostProcessor`](spring-interface/spring-interface-mergedBeanDefinitionPostProcessor/README.md):在合并Bean定义时对BeanDefinition进行处理。 - [`SmartInstantiationAwareBeanPostProcessor`](spring-interface/spring-interface-smartInstantiationAwareBeanPostProcessor/README.md):提供更智能的实例化控制。 - [`SmartInitializingSingleton`](spring-interface/spring-interface-smartInitializingSingleton/README.md):在所有单例Bean初始化完成后,执行自定义逻辑。 + + Aware接口系列 - [`BeanNameAware`](spring-aware/spring-aware-beanNameAware/README.md):让Bean获取自身在容器中的名字。 - [`BeanClassLoaderAware`](spring-aware/spring-aware-beanClassLoaderAware/README.md):允许Bean获取其类加载器。 @@ -147,6 +161,7 @@ - [`ApplicationStartupAware`](spring-aware/spring-aware-applicationStartupAware/README.md):允许Bean获取应用程序启动信息。 - [`ApplicationContextAware`](spring-aware/spring-aware-applicationContextAware/README.md):允许Bean获取应用程序上下文。 - [`ImportAware`](spring-aware/spring-aware-importAware/README.md):允许被导入的配置类获取导入它的类的信息。 + + 核心注解 - [`@Configuration`](spring-annotation/spring-annotation-configuration/README.md):声明类为配置类,定义Bean和Bean之间的依赖关系。 - [`@ComponentScan`](spring-annotation/spring-annotation-componentScan/README.md):启用组件扫描,自动发现并注册标记为组件的类。 @@ -163,6 +178,7 @@ - `@Role`:为Bean提供角色提示,用于区分相似类型的Bean。 - `@Indexed`: 标记Bean用于索引。 - `@Order`:指定Bean的加载顺序。 + + JSR规范 - [`@Inject`](spring-jsr/spring-jsr330-inject/README.md):JSR-330标准的依赖注入注解。 - [`@Named`](spring-jsr/spring-jsr330-named/README.md):JSR-330标准的命名注解。 diff --git a/spring-spel/pom.xml b/spring-spel/pom.xml index 81c9124..057b07d 100644 --- a/spring-spel/pom.xml +++ b/spring-spel/pom.xml @@ -16,6 +16,10 @@ spring-spel-expressionParser spring-spel-evaluationContext spring-spel-propertyAccessor + spring-spel-constructorResolver + spring-spel-methodResolver + spring-spel-beanResolver + spring-spel-typeLocator \ No newline at end of file diff --git a/spring-spel/spring-spel-beanResolver/README.md b/spring-spel/spring-spel-beanResolver/README.md new file mode 100644 index 0000000..09f6c95 --- /dev/null +++ b/spring-spel/spring-spel-beanResolver/README.md @@ -0,0 +1,186 @@ +## BeanResolver + +- [BeanResolver](#BeanResolver) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **Spring表达式语言(SpEL)** + + + 了解SpEL的语法和用法,包括如何使用表达式来访问和操作对象的属性、调用方法等。因为`BeanResolver`通常用于在SpEL中解析Bean,所以对SpEL的理解至关重要。 + +2. **Spring容器** + + + 理解Spring容器的概念和工作原理,包括BeanFactory和ApplicationContext之间的区别、Bean的生命周期、Bean的作用域等。因为`BeanResolver`通常用于从Spring容器中解析Bean,所以对Spring容器的了解是必要的。 + +3. **反射(Reflection)** + + + 理解Java反射的基本原理和用法,包括如何在运行时获取和操作类的信息。`BeanResolver`通常需要使用反射来动态地创建和操作对象,所以对反射有一定的了解是很有帮助的。 + +4. **设计模式** + + + 了解设计模式的基本原理和常见的设计模式,如工厂模式、策略模式等。因为`BeanResolver`接口通常用于实现依赖注入等功能,对设计模式的了解有助于编写更灵活、可扩展的解决方案。 + +### 三、基本描述 + +`BeanResolver`接口是Spring框架中的一个关键接口,用于在Spring表达式语言(SpEL)中解析Bean。它定义了一个`resolve`方法,接收一个`EvaluationContext`对象和一个Bean的名称作为参数,然后返回相应的Bean实例。通过实现`BeanResolver`接口,可以在SpEL表达式中轻松地引用和操作Spring容器中的Bean,使得表达式更加灵活和强大。 + +### 四、主要功能 + +1. **解析Bean** + + + 提供了解析Bean的方法,可以根据给定的Bean名称从Spring容器中获取相应的Bean实例。这样可以在运行时动态地获取和操作Spring容器中的Bean。 + +2. **支持SpEL** + + + 作为Spring表达式语言(SpEL)的一部分,`BeanResolver`允许在SpEL表达式中引用和操作Spring容器中的Bean。通过`BeanResolver`,可以在表达式中使用特殊的语法来引用Bean,并进行各种操作,如访问属性、调用方法等。 + +3. **提供上下文支持** + + + `BeanResolver`接口通常与`EvaluationContext`对象一起使用,它提供了表达式所需的上下文信息,包括变量、函数等。这样可以确保在解析Bean时具有必要的上下文信息。 + +4. **定制解析逻辑** + + + 尽管默认情况下`BeanResolver`的实现由Spring容器提供,但你可以根据需要自定义`BeanResolver`的实现。这样可以灵活地定制Bean的解析逻辑,以满足特定的业务需求。例如,你可以实现一个特殊的`BeanResolver`,用于按照特定的规则解析Bean,或者从非标准的地方获取Bean实例。 + +5. **解耦业务逻辑** + + + 通过使用`BeanResolver`,可以将业务逻辑与具体的Bean获取方式解耦。这样,在不同的环境或场景下,可以轻松地切换和替换`BeanResolver`的实现,而不影响业务逻辑的其他部分。 + +### 五、接口源码 + +`BeanResolver` 接口允许在Spring的表达式语言(SpEL)中解析Bean引用。通过注册到评估上下文中,它可以根据给定的Bean名称查找相应的实例,支持使用 `@myBeanName` 和 `&myBeanName` 表达式来引用Bean,其中 `&` 前缀允许访问工厂Bean。 + +```java +/** + * BeanResolver接口可以注册到评估上下文中,并且会为Bean引用 {@code @myBeanName} 和 {@code &myBeanName} 表达式提供解析支持。 + * 当需要访问工厂Bean时,{@code &} 变体语法允许访问工厂Bean。 + * + * @author Andy Clement + * @since 3.0.3 + */ +public interface BeanResolver { + + /** + * 根据给定的名称查找Bean,并返回相应的实例。 + * 对于尝试访问工厂Bean,名称需要以 & 前缀。 + * @param context 当前的评估上下文 + * @param beanName 要查找的Bean的名称 + * @return 表示Bean的对象 + * @throws AccessException 如果解析Bean时出现意外问题 + */ + Object resolve(EvaluationContext context, String beanName) throws AccessException; + +} +``` + + `BeanFactoryResolver` 实现了 `BeanResolver` 接口,用于在Spring的Bean工厂中解析Bean。它包含一个构造函数用于初始化,并实现了 `resolve` 方法来根据给定的Bean名称从Bean工厂中获取相应的Bean实例。 + +```java +/** + * 用于与Spring的Bean工厂交互的EL bean解析器。 + * + * @author Juergen Hoeller + * @since 3.0.4 + */ +public class BeanFactoryResolver implements BeanResolver { + + private final BeanFactory beanFactory; + + /** + * 为给定的工厂创建一个新的BeanFactoryResolver。 + * + * @param beanFactory 要解析Bean名称的Bean工厂 + */ + public BeanFactoryResolver(BeanFactory beanFactory) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + this.beanFactory = beanFactory; + } + + @Override + public Object resolve(EvaluationContext context, String beanName) throws AccessException { + try { + return this.beanFactory.getBean(beanName); + } catch (BeansException ex) { + throw new AccessException("无法根据Bean工厂解析Bean引用", ex); + } + } + +} +``` + +### 六、主要实现 + +1. **BeanFactoryResolver** + + `BeanFactoryResolver` 是一个实现了 `BeanResolver` 接口的类,在 Spring 框架中用于从 Bean 工厂中解析 Bean。通过构造函数接收一个 BeanFactory 实例,在调用 resolve 方法时,根据给定的 Bean 名称从 BeanFactory 中获取相应的 Bean 实例。 + +### 七、最佳实践 + +使用 `BeanResolver` 接口和 `BeanFactoryResolver` 实现类来解析 Spring 容器中的 Bean。首先,通过注解配置方式创建了一个 BeanFactory,然后使用 SpEL 表达式解析器解析表达式 `@myBean`,并将 `BeanFactoryResolver` 设置为评估上下文的 BeanResolver。最后,通过解析 SpEL 表达式获取到相应的 Bean 实例,并进行打印输出。 + +```java +public class BeanResolverDemo { + public static void main(String[] args) { + // 创建 BeanFactory + // 这里使用注解配置的方式创建 BeanFactory + BeanFactory beanFactory = new AnnotationConfigApplicationContext(MyBean.class).getBeanFactory(); + + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + // 创建一个标准的评估上下文 + StandardEvaluationContext context = new StandardEvaluationContext(); + // 将 BeanFactoryResolver 设置为上下文的 BeanResolver + context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + + // 解析 SpEL 表达式,获取 Bean 实例 + Object myBean = parser.parseExpression("@myBean").getValue(context); + + // 打印 Bean 实例 + System.out.println("myBean = " + myBean); + } +} +``` + +运行结果,表达式 `@myBean` 成功解析,并获取到了名为 `myBean` 的 Bean 实例。 + +```properties +myBean = com.xcs.spring.MyBean@34123d65 +``` + +### 八、与其他组件的关系 + +1. **ExpressionParser** + + + `ExpressionParser` 是 Spring 框架中用于解析表达式的接口,它通常与 `BeanResolver` 接口一起使用。`ExpressionParser` 负责解析 SpEL 表达式,而 `BeanResolver` 则负责解析表达式中的 Bean 引用。 + +2. **EvaluationContext** + + + `EvaluationContext` 是 Spring 表达式解析过程中的上下文对象,用于提供表达式所需的变量、函数等信息。`BeanResolver` 接口通常作为 `EvaluationContext` 的一部分,用于解析表达式中的 Bean 引用。 + +3. **BeanFactory**: + + + `BeanFactory` 是 Spring 框架中的核心接口之一,用于管理和获取 Bean 实例。`BeanFactoryResolver` 类实现了 `BeanResolver` 接口,用于在 BeanFactory 中解析 Bean 引用。 + +### 九、常见问题 + +1. **如何自定义 BeanResolver 的实现?** + - 我们可能想要根据特定需求自定义 `BeanResolver` 的实现,例如从不同的数据源中获取 Bean 实例。解决方法通常包括创建一个新的类实现 `BeanResolver` 接口,并根据需要覆盖 `resolve` 方法。 +2. **如何处理 Bean 解析失败的情况?** + - 当 `BeanResolver` 无法解析请求的 Bean 时,可能会抛出异常。开发人员需要考虑如何处理这种异常情况,例如记录日志、返回默认值或者向用户提供友好的错误消息。 +3. **如何在 SpEL 表达式中引用 Bean?** + - 我们可能需要在 SpEL 表达式中引用 Spring 容器中的 Bean,以便执行特定的逻辑。通常情况下,可以使用 `@beanName` 或 `&beanName` 表达式来引用 Bean,其中 `@` 表示获取 Bean 实例,`&` 表示获取 Bean 的工厂实例。 +4. **如何解决循环依赖问题?** + - 当存在循环依赖的 Bean 时,可能会导致 `BeanResolver` 无法正常解析 Bean。我们需要谨慎设计 Bean 之间的依赖关系,或者使用延迟初始化等技术来解决循环依赖问题。 \ No newline at end of file diff --git a/spring-spel/spring-spel-beanResolver/pom.xml b/spring-spel/spring-spel-beanResolver/pom.xml new file mode 100644 index 0000000..800d2ee --- /dev/null +++ b/spring-spel/spring-spel-beanResolver/pom.xml @@ -0,0 +1,15 @@ + + + + com.xcs.spring + spring-spel + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel-beanResolver + + + \ No newline at end of file diff --git a/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/BeanResolverDemo.java b/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/BeanResolverDemo.java new file mode 100644 index 0000000..1cb7495 --- /dev/null +++ b/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/BeanResolverDemo.java @@ -0,0 +1,30 @@ +package com.xcs.spring; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +public class BeanResolverDemo { + public static void main(String[] args) { + // 创建 BeanFactory + // 这里使用注解配置的方式创建 BeanFactory + BeanFactory beanFactory = new AnnotationConfigApplicationContext(MyBean.class).getBeanFactory(); + + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + // 创建一个标准的评估上下文 + StandardEvaluationContext context = new StandardEvaluationContext(); + // 将 BeanFactoryResolver 设置为上下文的 BeanResolver + context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + + // 解析 SpEL 表达式,获取 Bean 实例 + Object myBean = parser.parseExpression("@myBean").getValue(context); + + // 打印 Bean 实例 + System.out.println("myBean = " + myBean); + } +} diff --git a/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/MyBean.java b/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/MyBean.java new file mode 100644 index 0000000..07a9179 --- /dev/null +++ b/spring-spel/spring-spel-beanResolver/src/main/java/com/xcs/spring/MyBean.java @@ -0,0 +1,4 @@ +package com.xcs.spring; + +public class MyBean { +} diff --git a/spring-spel/spring-spel-constructorResolver/README.md b/spring-spel/spring-spel-constructorResolver/README.md new file mode 100644 index 0000000..9416798 --- /dev/null +++ b/spring-spel/spring-spel-constructorResolver/README.md @@ -0,0 +1,256 @@ +## ConstructorResolver + +- [ConstructorResolver](#ConstructorResolver) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **理解构造函数(Constructor)** + + + 构造函数是用于创建类的实例的特殊方法。了解构造函数的概念、使用方式以及与普通方法的区别是很重要的。 + +2. **反射(Reflection)** + + + 反射是Java语言的一个特性,允许程序在运行时检查和操作类、对象、方法和属性。学习如何使用`java.lang.reflect`包中的类来获取和操作构造函数是必要的。 + +3. **Spring表达式语言(SpEL)** + + + `ConstructorResolver`通常与Spring表达式语言(SpEL)一起使用。因此,了解SpEL的基本语法和用法是很有帮助的。 + +4. **设计模式** + + + 了解一些设计模式,特别是创建型模式(如工厂模式、建造者模式等),可以帮助理解对象创建的不同方法和策略。 + +### 三、基本描述 + +`ConstructorResolver`接口是Spring框架中用于解析和执行构造函数的核心接口之一,它允许在运行时动态地确定和调用对象的构造函数。该接口定义了一个`resolve(ConstructorExecutor, TypedValue[])`方法,该方法用于根据给定的构造函数执行器和参数来解析构造函数。通过实现`ConstructorResolver`接口,Spring能够以灵活和动态的方式实现对象的实例化,从而使得依赖注入和对象创建更加灵活和可定制化。在Spring框架中的实现类中,通常会利用反射机制来动态地实例化对象,并根据对象的类型和参数列表来选择合适的构造函数进行调用。 + +### 四、主要功能 + +1. **解析构造函数** + + + 接口定义了一个方法`resolve(ConstructorExecutor, TypedValue[])`,用于解析给定的构造函数。这意味着可以根据传入的参数和构造函数的签名来确定要调用的构造函数。 + +2. **执行构造函数** + + + 一旦构造函数被解析,接口负责调用相应的构造函数以创建对象实例。这涉及到创建实例、传递参数以及处理构造函数的执行。 + +3. **支持动态对象实例化** + + + `ConstructorResolver`接口使得在运行时能够动态地选择合适的构造函数实例化对象,从而提供了灵活性和可定制性。 + +### 五、接口源码 + +`ConstructorResolver`接口定义了一个方法`resolve`,用于在给定的上下文中确定特定类型的合适构造函数,该构造函数能够处理指定的参数类型列表。接口返回一个`ConstructorExecutor`,可以用于调用找到的构造函数。 + +```java +/** + * 构造函数解析器尝试定位一个构造函数,并返回一个ConstructorExecutor, + * 该Executor可以用于调用该构造函数。ConstructorExecutor将被缓存, + * 但如果它“过时”,则会重新调用解析器。 + * + * @author Andy Clement + * @since 3.0 + */ +@FunctionalInterface +public interface ConstructorResolver { + + /** + * 在提供的上下文中确定指定类型上可以处理指定参数的合适构造函数。 + * 返回一个ConstructorExecutor,可以用于调用该构造函数(如果找不到构造函数,则返回null)。 + * + * @param context 当前的评估上下文 + * @param typeName 要查找构造函数的类型 + * @param argumentTypes 构造函数必须能够处理的参数 + * @return 一个ConstructorExecutor,可以调用该构造函数,如果找不到则返回null + * @throws AccessException 访问异常 + */ + @Nullable + ConstructorExecutor resolve(EvaluationContext context, String typeName, List argumentTypes) + throws AccessException; + +} +``` + +`ReflectiveConstructorResolver`是Spring框架中的一个实现类,用于通过反射机制来定位应该被调用的构造函数。它能够根据提供的参数类型列表,对目标类的构造函数进行匹配,并选择合适的构造函数进行实例化。在匹配过程中,可能会发生三种类型的匹配:完全匹配、不完全匹配和需要类型转换的匹配。根据匹配结果,`ReflectiveConstructorResolver`将返回一个`ConstructorExecutor`对象,用于执行选定的构造函数。 + +```java +/** + * 使用反射来定位应该被调用的构造函数的构造函数解析器。 + * @author Andy Clement + * @author Juergen Hoeller + * @since 3.0 + */ +public class ReflectiveConstructorResolver implements ConstructorResolver { + + /** + * 在类型上定位一个构造函数。可能会出现三种类型的匹配: + *
    + *
  1. 完全匹配,其中参数的类型与构造函数的类型相匹配 + *
  2. 不完全匹配,其中我们要查找的类型是构造函数上定义的类型的子类型 + *
  3. 匹配,其中我们能够将参数转换成构造函数预期的类型,根据已注册的类型转换器。 + *
+ */ + @Override + @Nullable + public ConstructorExecutor resolve(EvaluationContext context, String typeName, List argumentTypes) + throws AccessException { + + try { + // 获取类型转换器和类类型 + TypeConverter typeConverter = context.getTypeConverter(); + Class type = context.getTypeLocator().findType(typeName); + Constructor[] ctors = type.getConstructors(); + + // 按参数数量排序构造函数 + Arrays.sort(ctors, Comparator.comparingInt(Constructor::getParameterCount)); + + // 初始化匹配变量 + Constructor closeMatch = null; + Constructor matchRequiringConversion = null; + + // 遍历构造函数 + for (Constructor ctor : ctors) { + int paramCount = ctor.getParameterCount(); + List paramDescriptors = new ArrayList<>(paramCount); + for (int i = 0; i < paramCount; i++) { + paramDescriptors.add(new TypeDescriptor(new MethodParameter(ctor, i))); + } + ReflectionHelper.ArgumentsMatchInfo matchInfo = null; + if (ctor.isVarArgs() && argumentTypes.size() >= paramCount - 1) { + // 处理可变参数的匹配 + matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter); + } else if (paramCount == argumentTypes.size()) { + // 处理普通参数的匹配 + matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); + } + if (matchInfo != null) { + if (matchInfo.isExactMatch()) { + return new ReflectiveConstructorExecutor(ctor); + } else if (matchInfo.isCloseMatch()) { + closeMatch = ctor; + } else if (matchInfo.isMatchRequiringConversion()) { + matchRequiringConversion = ctor; + } + } + } + + // 返回匹配的构造函数执行器 + if (closeMatch != null) { + return new ReflectiveConstructorExecutor(closeMatch); + } else if (matchRequiringConversion != null) { + return new ReflectiveConstructorExecutor(matchRequiringConversion); + } else { + return null; + } + } catch (EvaluationException ex) { + throw new AccessException("Failed to resolve constructor", ex); + } + } +} +``` + +### 六、主要实现 + ++ **ReflectiveConstructorResolver** + + 用于解析和执行构造函数的主要实现类。通过利用Java的反射机制,它能够动态地确定并调用类的构造函数,从而实现对象的实例化。 + +### 七、最佳实践 + +创建了一个`SpelExpressionParser`对象,它是Spring表达式语言的解析器。然后,我们使用SpEL表达式`"new com.xcs.spring.MyBean('spring-reading')"`来创建一个`MyBean`对象,参数为`"spring-reading"`。接着,我们通过调用`getValue(MyBean.class)`方法来获取实例化后的`MyBean`对象。最后,我们打印输出了这个实例化的`MyBean`对象。 + +```java +public class ConstructorResolverDemo { + public static void main(String[] args) { + ExpressionParser parser = new SpelExpressionParser(); + MyBean myBean = parser.parseExpression("new com.xcs.spring.MyBean('spring-reading')").getValue(MyBean.class); + System.out.println(myBean); + } +} +``` + +定义了一个简单的Java Bean,名为`MyBean`,具有一个私有属性`name`和一个带参构造函数、以及相应的getter和setter方法。此外,还重写了`toString()`方法,以便在打印对象时输出其属性值。 + +```java +public class MyBean { + + private String name; + + public MyBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyBean{" + + "name='" + name + '\'' + + '}'; + } +} +``` + +运行结果,成功地创建了一个`MyBean`对象,并将其属性`name`设置为`"spring-reading"`。 + +```properties +MyBean{name='spring-reading'} +``` + +### 八、与其他组件的关系 + +1. **EvaluationContext** + + + `ConstructorResolver`接口通常用于解析构造函数的过程中需要一个评估上下文,用于提供解析过程中所需的信息和环境,例如变量、函数、类型等。在Spring框架中,通常使用`EvaluationContext`接口或其实现类来表示评估上下文。 + +2. **TypedValue** + + + `ConstructorResolver`接口中的方法可能需要用到`TypedValue`对象,该对象用于表示表达式中的值,并提供了对值的类型信息的访问。在SpEL中,`TypedValue`通常用于表示表达式中的字面值、变量值等。 + +3. **ConstructorExecutor** + + + `ConstructorResolver`接口的实现类通常会返回一个`ConstructorExecutor`对象,该对象用于执行已解析的构造函数。`ConstructorExecutor`接口定义了一个`execute`方法,用于执行构造函数并返回结果对象。 + +4. **SpelExpressionParser** + + + `SpelExpressionParser`是Spring框架中用于解析SpEL表达式的核心类之一。在上下文中使用`SpelExpressionParser`来解析表达式时,可能会涉及到`ConstructorResolver`接口的实现类,用于处理表达式中的构造函数引用。 + +5. **ReflectiveConstructorResolver** + + + `ReflectiveConstructorResolver`是Spring框架中`ConstructorResolver`接口的主要实现类之一。它使用Java的反射机制来动态地解析和执行构造函数。 + +### 九、常见问题 + +1. **如何处理构造函数参数的类型匹配问题?** + + - `ConstructorResolver`接口的实现类通常会根据提供的参数类型列表来选择合适的构造函数。如果参数类型与构造函数参数类型不匹配,可能会导致解析失败或选择错误的构造函数。因此,需要确保传递正确类型的参数。 + +2. **如何处理构造函数重载的情况?** + + - 当类中存在多个构造函数时,`ConstructorResolver`接口的实现类可能需要选择最适合的构造函数。通常情况下,会根据提供的参数类型列表和构造函数参数列表的匹配程度来进行选择。如果存在多个匹配的构造函数,可能会导致解析失败或选择不确定的构造函数。 + +3. **ConstructorResolver接口与Java反射机制的关系是什么?** + + - `ConstructorResolver`接口的实现类通常会利用Java的反射机制来动态地解析和执行构造函数。通过反射,可以在运行时检查类的结构,并调用特定的构造函数来实例化对象。因此,`ConstructorResolver`接口与Java反射机制密切相关。 + +4. **如何处理构造函数中可能抛出的异常?** + + - 在调用构造函数时,可能会出现各种异常,如参数类型不匹配、访问权限问题等。`ConstructorResolver`接口的实现类可能会处理这些异常,具体取决于实现。通常情况下,需要在调用构造函数之前进行适当的异常处理。 diff --git a/spring-spel/spring-spel-constructorResolver/pom.xml b/spring-spel/spring-spel-constructorResolver/pom.xml new file mode 100644 index 0000000..db9f537 --- /dev/null +++ b/spring-spel/spring-spel-constructorResolver/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-spel + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel-constructorResolver + + \ No newline at end of file diff --git a/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/ConstructorResolverDemo.java b/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/ConstructorResolverDemo.java new file mode 100644 index 0000000..03ebcfe --- /dev/null +++ b/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/ConstructorResolverDemo.java @@ -0,0 +1,22 @@ +package com.xcs.spring; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * @author xcs + * @date 2024年3月12日11:28:47 + **/ +public class ConstructorResolverDemo { + public static void main(String[] args) { + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + // 解析SpEL表达式,并使用构造函数实例化对象 + // 这里的SpEL表达式是一个构造函数调用,创建了一个MyBean对象,参数为'spring-reading' + MyBean myBean = parser.parseExpression("new com.xcs.spring.MyBean('spring-reading')").getValue(MyBean.class); + + // 打印输出实例化的MyBean对象 + System.out.println(myBean); + } +} diff --git a/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/MyBean.java b/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/MyBean.java new file mode 100644 index 0000000..445b9e6 --- /dev/null +++ b/spring-spel/spring-spel-constructorResolver/src/main/java/com/xcs/spring/MyBean.java @@ -0,0 +1,25 @@ +package com.xcs.spring; + +public class MyBean { + + private String name; + + public MyBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyBean{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/spring-spel/spring-spel-evaluationContext/src/main/java/com/xcs/spring/EvaluationContextDemo.java b/spring-spel/spring-spel-evaluationContext/src/main/java/com/xcs/spring/EvaluationContextDemo.java index 08abc1a..305d466 100644 --- a/spring-spel/spring-spel-evaluationContext/src/main/java/com/xcs/spring/EvaluationContextDemo.java +++ b/spring-spel/spring-spel-evaluationContext/src/main/java/com/xcs/spring/EvaluationContextDemo.java @@ -5,6 +5,10 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import java.util.List; +/** + * @author xcs + * @date 2024年3月12日11:28:47 + **/ public class EvaluationContextDemo { public static void main(String[] args) { // 创建评估上下文 diff --git a/spring-spel/spring-spel-methodResolver/README.md b/spring-spel/spring-spel-methodResolver/README.md new file mode 100644 index 0000000..2c96f05 --- /dev/null +++ b/spring-spel/spring-spel-methodResolver/README.md @@ -0,0 +1,426 @@ +## MethodResolver + +- [MethodResolver](#MethodResolver) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **Spring 表达式语言(SpEL)** + + + `MethodResolver` 接口通常用于 SpEL 中,因此需要了解 SpEL 的语法和基本用法。这包括在 XML 或注解配置中如何使用 SpEL 表达式,以及如何在代码中动态解析 SpEL 表达式。 + +2. **反射(Reflection)** + + + `MethodResolver` 通常需要使用反射来动态地查找和调用对象的方法。因此,需要了解 Java 中的反射机制,包括 `Class`、`Method`、`Field` 等类的使用方法,以及如何通过反射调用对象的方法。 + +### 三、基本描述 + +`MethodResolver` 接口是 Spring Framework 表达式语言(SpEL)模块的关键组成部分之一,用于在运行时解析并执行对象的方法。它定义了一个方法 `resolve`,接受方法名和参数类型列表作为参数,并返回一个 `MethodExecutor` 对象,代表解析到的方法。`MethodResolver` 的实现负责根据给定的方法名和参数类型,确定要调用的方法,并创建一个对应的 `MethodExecutor` 实例,以便在表达式求值期间执行该方法。 + +### 四、主要功能 + +1. **方法解析** + + + `MethodResolver` 接口负责解析表达式中的方法调用。它根据方法名和参数类型列表,确定要调用的方法。 + +2. **动态方法调用** + + + `MethodResolver` 允许在运行时动态地调用对象的方法。这使得 SpEL 可以根据表达式的需求,灵活地调用对象的不同方法。 + +3. **方法执行器创建** + + + `MethodResolver` 返回一个 `MethodExecutor` 对象,该对象代表解析到的方法。`MethodExecutor` 负责实际执行方法,并返回执行结果。 + +4. **扩展性** + + + `MethodResolver` 接口是可扩展的,用户可以根据自己的需求实现自定义的 `MethodResolver`,以实现特定的方法解析逻辑。这使得 SpEL 在处理不同类型的对象和方法调用时更加灵活和可定制。 + +### 五、接口源码 + +`MethodResolver` 接口定义了一个方法解析器的规范,其主要功能是在给定的上下文中,根据对象和方法名,确定可以处理指定参数的合适方法,并返回一个执行器以供调用。这个接口允许在运行时动态地解析和调用对象的方法,为表达式语言提供了灵活性和扩展性。 + +```java +/** + * 方法解析器尝试定位一个方法,并返回一个命令执行器,该执行器可用于调用该方法。命令执行器将被缓存, + * 但如果它过期了,则会再次调用解析器。 + * + * @author Andy Clement + * @since 3.0 + */ +public interface MethodResolver { + + /** + * 在提供的上下文中,确定目标对象上可以处理指定参数的合适方法。返回一个 {@link MethodExecutor}, + * 该执行器可用于调用该方法,如果找不到方法,则返回 {@code null}。 + * @param context 当前的评估上下文 + * @param targetObject 调用方法的对象 + * @param name 方法名 + * @param argumentTypes 方法的参数类型列表 + * @return 可以调用方法的 MethodExecutor,如果找不到方法,则返回 null + * @throws AccessException 如果访问方法时发生错误 + */ + @Nullable + MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, + List argumentTypes) throws AccessException; +} +``` + +`ReflectiveMethodResolver` 是一个基于反射的方法解析器,用于在给定的上下文中尝试定位目标对象上的适当方法,并返回一个方法执行器以供调用。它通过反射机制检查目标对象的方法,根据方法名和参数类型进行匹配。在匹配过程中,它考虑了方法的参数类型以及是否需要类型转换。此外,它还支持注册方法过滤器来过滤特定类型上的方法。在解析方法时,它会对方法进行排序,处理桥接方法,并且可以选择是否使用距离计算来查找更准确的匹配。如果找到匹配的方法,它将返回一个方法执行器;否则,返回 null。 + +```java +/** + * 基于反射的 {@link MethodResolver},在 {@link StandardEvaluationContext} 中默认使用, + * 除非已经指定了显式的方法解析器。 + * + * 作者:Andy Clement, Juergen Hoeller, Chris Beams + * 自从:3.0版本 + * @see StandardEvaluationContext#addMethodResolver(MethodResolver) + */ +public class ReflectiveMethodResolver implements MethodResolver { + + // 使用距离将确保发现更准确的匹配, + // 更接近遵循Java规则。 + private final boolean useDistance; + + @Nullable + private Map, MethodFilter> filters; + + public ReflectiveMethodResolver() { + this.useDistance = true; + } + + /** + * 此构造函数允许配置 ReflectiveMethodResolver, + * 使其使用距离计算来检查两个接近匹配中的更好的匹配 + * (当存在多个匹配时)。使用距离计算旨在确保匹配更接近 + * Java 编译器在考虑装箱/拆箱和方法候选人是否声明为 + * 处理传入参数类型的类型(参数的类型)时会发生什么。 + * @param useDistance 如果计算匹配时应使用距离计算,则为 {@code true};否则为 {@code false} + */ + public ReflectiveMethodResolver(boolean useDistance) { + this.useDistance = useDistance; + } + + /** + * 注册给定类型的方法过滤器。 + * @param type 要过滤的类型 + * @param filter 相应的方法过滤器, + * 如果要清除给定类型的任何过滤器,则为 {@code null} + */ + public void registerMethodFilter(Class type, @Nullable MethodFilter filter) { + if (this.filters == null) { + this.filters = new HashMap<>(); + } + if (filter != null) { + this.filters.put(type, filter); + } + else { + this.filters.remove(type); + } + } + + /** + * 在给定的上下文中,尝试定位目标对象上的适当方法,并返回一个方法执行器以供调用。 + * 方法执行器将被缓存,但如果其状态过期,解析器将被再次调用。 + *

+ * 在给定的上下文中,该方法确定可以处理指定参数的适当方法,并返回一个 {@link MethodExecutor} 对象, + * 该对象代表解析到的方法。如果找不到方法,则返回 {@code null}。 + * @param context 当前的评估上下文 + * @param targetObject 调用方法的对象 + * @param name 方法名 + * @param argumentTypes 方法的参数类型列表 + * @return 可以调用方法的 MethodExecutor,如果找不到方法,则返回 null + * @throws AccessException 如果访问方法时发生错误 + */ + @Override + @Nullable + public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, + List argumentTypes) throws AccessException { + try { + // 获取类型转换器 + TypeConverter typeConverter = context.getTypeConverter(); + // 获取目标对象的类 + Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); + // 获取目标对象上的所有方法 + ArrayList methods = new ArrayList<>(getMethods(type, targetObject)); + + // 如果为该类型注册了过滤器,请调用它 + MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); + if (filter != null) { + List filtered = filter.filter(methods); + methods = (filtered instanceof ArrayList ? (ArrayList) filtered : new ArrayList<>(filtered)); + } + + // 将方法排序为合理的顺序 + if (methods.size() > 1) { + methods.sort((m1, m2) -> { + int m1pl = m1.getParameterCount(); + int m2pl = m2.getParameterCount(); + // 变长参数方法放在最后 + if (m1pl == m2pl) { + if (!m1.isVarArgs() && m2.isVarArgs()) { + return -1; + } else if (m1.isVarArgs() && !m2.isVarArgs()) { + return 1; + } else { + return 0; + } + } + return Integer.compare(m1pl, m2pl); + }); + } + + // 解析任何桥接方法 + for (int i = 0; i < methods.size(); i++) { + methods.set(i, BridgeMethodResolver.findBridgedMethod(methods.get(i))); + } + + // 删除重复的方法(由于解析的桥接方法可能导致) + Set methodsToIterate = new LinkedHashSet<>(methods); + + Method closeMatch = null; + int closeMatchDistance = Integer.MAX_VALUE; + Method matchRequiringConversion = null; + boolean multipleOptions = false; + + // 遍历方法,查找适合的方法 + for (Method method : methodsToIterate) { + if (method.getName().equals(name)) { + int paramCount = method.getParameterCount(); + List paramDescriptors = new ArrayList<>(paramCount); + // 构造方法参数类型描述符列表 + for (int i = 0; i < paramCount; i++) { + paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i))); + } + ReflectionHelper.ArgumentsMatchInfo matchInfo = null; + if (method.isVarArgs() && argumentTypes.size() >= (paramCount - 1)) { + // 处理变长参数 + matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter); + } else if (paramCount == argumentTypes.size()) { + // 检查参数是否匹配 + matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); + } + if (matchInfo != null) { + if (matchInfo.isExactMatch()) { + return new ReflectiveMethodExecutor(method); + } else if (matchInfo.isCloseMatch()) { + if (this.useDistance) { + // 计算匹配的距离 + int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes); + if (closeMatch == null || matchDistance < closeMatchDistance) { + // 保存更好的匹配 + closeMatch = method; + closeMatchDistance = matchDistance; + } + } else { + // 如果没有更好的匹配,将其视为接近匹配 + if (closeMatch == null) { + closeMatch = method; + } + } + } else if (matchInfo.isMatchRequiringConversion()) { + if (matchRequiringConversion != null) { + multipleOptions = true; + } + matchRequiringConversion = method; + } + } + } + } + // 返回找到的方法执行器 + if (closeMatch != null) { + return new ReflectiveMethodExecutor(closeMatch); + } else if (matchRequiringConversion != null) { + if (multipleOptions) { + // 如果有多个匹配,则抛出异常 + throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); + } + return new ReflectiveMethodExecutor(matchRequiringConversion); + } else { + return null; + } + } catch (EvaluationException ex) { + // 解析方法时出现异常 + throw new AccessException("Failed to resolve method", ex); + } + } + + + private Set getMethods(Class type, Object targetObject) { + if (targetObject instanceof Class) { + Set result = new LinkedHashSet<>(); + // 添加这些方法,以便在类型上可调用静态方法:例如 Float.valueOf(..) + Method[] methods = getMethods(type); + for (Method method : methods) { + if (Modifier.isStatic(method.getModifiers())) { + result.add(method); + } + } + // 还从 java.lang.Class 本身公开方法 + Collections.addAll(result, getMethods(Class.class)); + return result; + } + else if (Proxy.isProxyClass(type)) { + Set result = new LinkedHashSet<>(); + // 公开接口方法(不是代理声明的重写)以便适当的变长参数内省 + for (Class ifc : type.getInterfaces()) { + Method[] methods = getMethods(ifc); + for (Method method : methods) { + if (isCandidateForInvocation(method, type)) { + result.add(method); + } + } + } + return result; + } + else { + Set result = new LinkedHashSet<>(); + Method[] methods = getMethods(type); + for (Method method : methods) { + if (isCandidateForInvocation(method, type)) { + result.add(method); + } + } + return result; + } + } + + /** + * 返回此类型的方法集。默认实现返回给定类型的 {@link Class#getMethods()} 的结果, + * 但子类可以重写以更改结果,例如指定在其他地方声明的静态方法。 + * @param type 要返回方法的类 + * @since 3.1.1 + */ + protected Method[] getMethods(Class type) { + return type.getMethods(); + } + + /** + * 确定给定的 {@code Method} 是否是在给定目标类的实例上进行方法解析的候选方法。 + *

默认实现将任何方法都视为候选方法,即使对于 {@link Object} 基类的静态方法 + * 和非用户声明的方法也是如此。 + * @param method 要评估的方法 + * @param targetClass 正在内省的具体目标类 + * @since 4.3.15 + */ + protected boolean isCandidateForInvocation(Method method, Class targetClass) { + return true; + } + +} +``` + +### 六、主要实现 + +1. **DataBindingMethodResolver** + + + 是用于数据绑定环境的方法解析器,支持根据数据绑定的规则解析方法,例如 Spring 数据绑定框架中的规则,并可能使用更高级的技术如 Spring DataBinding,根据数据绑定的配置和元数据来查找适当的方法。 + +2. **ReflectiveMethodResolver** + + + 是基于反射机制的通用方法解析器,通过反射检查目标对象的方法,根据方法名和参数类型进行匹配,适用于大多数的 Java 对象和方法的解析,不需要特殊的配置或规则,只需要基于 Java 反射来解析方法即可。 + +### 七、最佳实践 + +使用Spring Expression Language(SpEL)来调用自定义对象(`MyBean`)中的方法。首先,通过SpEL表达式解析器创建一个解析器和一个上下文对象,然后实例化一个`MyBean`对象并将其设置为上下文中的变量。接着,创建一个SpEL表达式,调用`MyBean`对象的`add`方法,并传入两个参数。最后,将表达式与上下文关联,并计算表达式的值,即调用`MyBean`对象的方法并获取结果。 + +```java +public class MethodResolverDemo { + + public static void main(String[] args) { + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + StandardEvaluationContext context = new StandardEvaluationContext(); + + // 在 MyBean 中定义的方法将被 SpEL 表达式调用 + MyBean myBean = new MyBean(); + context.setVariable("myBean", myBean); + + // 创建一个 SpEL 表达式,调用 MyBean 中的方法 + SpelExpression expression = (SpelExpression) parser.parseExpression("#myBean.add(10, 5)"); + + // 为表达式设置上下文,并计算结果 + int result = (int) expression.getValue(context); + + // 打印输出实例化的MyBean对象 + System.out.println("result = " + result); + } +} +``` + + `MyBean` 类定义了一个简单的方法 `add`,它接受两个整数参数,并返回它们的和。 + +```java +public class MyBean { + public int add(int a, int b) { + return a + b; + } +} +``` + +运行结果,在 `MethodResolverDemo` 中调用了 `MyBean` 对象的 `add` 方法,并传入了两个参数(10 和 5),所以计算结果为 10 + 5 = 15。 + +```java +result = 15 +``` + +### 八、与其他组件的关系 + +1. **StandardEvaluationContext** + + + `MethodResolver` 接口通常与 `StandardEvaluationContext` 类一起使用。`StandardEvaluationContext` 提供了 SpEL 表达式的运行时上下文,其中可以存储变量、函数等信息,并且可以设置自定义的方法解析器,其中就包括了 `MethodResolver` 接口的实现类。 + +2. **ExpressionParser** + + + `ExpressionParser` 接口用于解析 SpEL 表达式。在 SpEL 中,可以使用 `ExpressionParser` 实现类来解析表达式,例如 `SpelExpressionParser`。在解析 SpEL 表达式时,通常需要提供一个 `EvaluationContext`,这个上下文中可能会包含一个或多个 `MethodResolver`,用于解析表达式中的方法调用。 + +3. **MethodExecutor** + + + `MethodResolver` 接口的 `resolve` 方法返回一个 `MethodExecutor` 对象,它用于执行解析到的方法。`MethodExecutor` 是一个接口,它定义了执行方法的方法 `execute`,具体的执行逻辑由其实现类提供。 + +4. **ReflectiveMethodResolver** & **DataBindingMethodResolver** + + + 这两个类是 `MethodResolver` 接口的实现类,分别用于基于反射和数据绑定环境下的方法解析。它们与 `MethodResolver` 接口的关系在于,它们实现了 `MethodResolver` 接口,并提供了具体的方法解析逻辑。在使用 SpEL 表达式时,可以选择性地使用这两个类中的一个或者自定义其他的方法解析器来解析方法调用。 + +### 九、常见问题 + +1. **如何自定义方法解析器?** + + - 可以实现 `MethodResolver` 接口,并覆写其中的 `resolve` 方法来定义自定义的方法解析逻辑。然后将这个自定义的方法解析器注册到 `StandardEvaluationContext` 中。 + +2. **方法解析器的优先级如何确定?** + + - 在 `StandardEvaluationContext` 中,可以注册多个方法解析器,它们的解析顺序由注册的顺序决定。解析时会按照注册的顺序逐个调用方法解析器,直到找到匹配的方法。 + +3. **如何处理方法重载?** + + - 当方法重载时,`MethodResolver` 需要根据传入的参数类型列表来确定最适合的方法。在解析方法调用时,会尝试匹配所有可能的方法,并根据参数类型的匹配程度来选择最佳的匹配。 + +4. **如何处理变长参数方法?** + + - 变长参数方法(例如使用 `...` 定义的参数)在方法解析中需要特殊处理。方法解析器需要检查传入的参数数量和类型是否与方法定义匹配,并正确处理变长参数的情况。 + +5. **如何处理方法参数的类型转换?** + + - 当方法参数的类型与传入参数的类型不完全匹配时,可能需要进行类型转换。方法解析器可以使用注册的类型转换器来进行必要的参数类型转换。 + +6. **如何处理桥接方法?** + + - 桥接方法是 Java 泛型中的一个特殊情况,可能会导致方法重载或者匹配错误。方法解析器需要正确处理桥接方法,以确保找到正确的方法进行调用。 + +7. **如何处理方法的静态调用?** + + - 当调用静态方法时,需要在方法解析器中考虑到静态方法的情况。方法解析器需要正确处理静态方法的调用,以确保可以找到并调用正确的静态方法。 \ No newline at end of file diff --git a/spring-spel/spring-spel-methodResolver/pom.xml b/spring-spel/spring-spel-methodResolver/pom.xml new file mode 100644 index 0000000..13728b0 --- /dev/null +++ b/spring-spel/spring-spel-methodResolver/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-spel + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel-methodResolver + + \ No newline at end of file diff --git a/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MethodResolverDemo.java b/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MethodResolverDemo.java new file mode 100644 index 0000000..e082b3c --- /dev/null +++ b/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MethodResolverDemo.java @@ -0,0 +1,33 @@ +package com.xcs.spring; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * @author xcs + * @date 2024年3月12日11:28:47 + **/ +public class MethodResolverDemo { + + public static void main(String[] args) { + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + StandardEvaluationContext context = new StandardEvaluationContext(); + + // 在 MyBean 中定义的方法将被 SpEL 表达式调用 + MyBean myBean = new MyBean(); + context.setVariable("myBean", myBean); + + // 创建一个 SpEL 表达式,调用 MyBean 中的方法 + SpelExpression expression = (SpelExpression) parser.parseExpression("#myBean.add(10, 5)"); + + // 为表达式设置上下文,并计算结果 + int result = (int) expression.getValue(context); + + // 打印输出实例化的MyBean对象 + System.out.println("result = " + result); + } +} diff --git a/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MyBean.java b/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MyBean.java new file mode 100644 index 0000000..d93fbdd --- /dev/null +++ b/spring-spel/spring-spel-methodResolver/src/main/java/com/xcs/spring/MyBean.java @@ -0,0 +1,7 @@ +package com.xcs.spring; + +public class MyBean { + public int add(int a, int b) { + return a + b; + } +} diff --git a/spring-spel/spring-spel-propertyAccessor/pom.xml b/spring-spel/spring-spel-propertyAccessor/pom.xml index 67a2419..0fa1040 100644 --- a/spring-spel/spring-spel-propertyAccessor/pom.xml +++ b/spring-spel/spring-spel-propertyAccessor/pom.xml @@ -2,19 +2,13 @@ - 4.0.0 com.xcs.spring spring-spel 0.0.1-SNAPSHOT + 4.0.0 spring-spel-propertyAccessor - - 11 - 11 - UTF-8 - - \ No newline at end of file diff --git a/spring-spel/spring-spel-typeLocator/README.md b/spring-spel/spring-spel-typeLocator/README.md new file mode 100644 index 0000000..0b6ea1f --- /dev/null +++ b/spring-spel/spring-spel-typeLocator/README.md @@ -0,0 +1,229 @@ +## TypeLocator + +- [TypeLocator](#TypeLocator) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **Spring 表达式语言(SpEL)** + + + 了解 SpEL 的基础语法和用法是必要的,因为 `TypeLocator` 接口通常用于 SpEL 中,用于动态获取类型信息。 + +2. **反射(Reflection)** + + + 了解 Java 中的反射机制,包括 `Class` 类、`Method` 类、`Field` 类等,因为 `TypeLocator` 接口通常需要使用反射来查找和操作类型信息。 + +3. **设计模式** ++ 熟悉常见的设计模式,如工厂模式、策略模式等,这些设计模式在实现 `TypeLocator` 接口时可能会有所应用。 + +### 三、基本描述 + +`TypeLocator` 接口是 Spring Framework 中的关键接口之一,用于动态定位类型信息,在 Spring 表达式语言(SpEL)等场景中扮演重要角色,通过提供方法如`findType(String typeName)`和`hasType(String typeName)`,允许 SpEL 在运行时动态获取和检查类型信息,增强了 Spring 应用程序的灵活性和功能性。 + +### 四、主要功能 + +1. **查找类型信息** + + + 通过 `findType(String typeName)` 方法,根据给定的类型名称查找对应的类型信息,使得 SpEL 在运行时能够动态获取所需类型的信息。 + +2. **检查类型是否存在** + + + 通过 `hasType(String typeName)` 方法,可以检查是否存在给定名称的类型。这对于确定能否解析给定的类型很有用。 + +### 五、接口源码 + +`TypeLocator` 接口定义了一种用于定位类型信息的机制,其中包含一个抽象方法 `findType(String typeName)`,用于根据给定的类型名称查找对应的类型,并返回表示该类型的 `Class` 对象。 + +```java +/** + * 实现此接口的类应能够定位类型。它们可以使用自定义的 {@link ClassLoader}, + * 和/或以任何方式处理常见的包前缀(例如 {@code java.lang})。 + * + *

参见 {@link org.springframework.expression.spel.support.StandardTypeLocator} + * 以获取示例实现。 + * + * @author Andy Clement + * @since 3.0 + */ +@FunctionalInterface +public interface TypeLocator { + + /** + * 根据名称查找类型。名称可以是完全限定的,也可以不是(例如 {@code String} 或 {@code java.lang.String})。 + * @param typeName 要定位的类型 + * @return 表示该类型的 {@code Class} 对象 + * @throws EvaluationException 如果查找类型时出现问题 + */ + Class findType(String typeName) throws EvaluationException; + +} + +``` + +`StandardTypeLocator` 是一个简单的实现类,实现了 `TypeLocator` 接口,用于根据给定的类型名称查找对应的类型信息。它支持使用上下文 ClassLoader 和注册的导入前缀来定位类型,当找不到类型时会尝试使用注册的导入前缀来定位。 + +```java +/** + * 一个简单的 {@link TypeLocator} 实现,它使用上下文 ClassLoader(或设置在其上的任何 ClassLoader)。 + * 它支持'well-known'包:如果找不到类型,则会尝试注册的导入来定位它。 + * + * @author Andy Clement + * @author Juergen Hoeller + * @since 3.0 + */ +public class StandardTypeLocator implements TypeLocator { + + @Nullable + private final ClassLoader classLoader; + + private final List knownPackagePrefixes = new ArrayList<>(1); + + /** + * 为默认的 ClassLoader(通常是线程上下文 ClassLoader)创建一个 StandardTypeLocator。 + */ + public StandardTypeLocator() { + this(ClassUtils.getDefaultClassLoader()); + } + + /** + * 为给定的 ClassLoader 创建一个 StandardTypeLocator。 + * @param classLoader 要委托的 ClassLoader + */ + public StandardTypeLocator(@Nullable ClassLoader classLoader) { + this.classLoader = classLoader; + // 类似于编写常规的 Java 代码,默认只知道 java.lang + registerImport("java.lang"); + } + + /** + * 注册一个新的导入前缀,用于搜索未限定类型时使用。 + * 期望的格式类似于 "java.lang"。 + * @param prefix 要注册的前缀 + */ + public void registerImport(String prefix) { + this.knownPackagePrefixes.add(prefix); + } + + /** + * 从此定位器的导入列表中删除指定的前缀。 + * @param prefix 要移除的前缀 + */ + public void removeImport(String prefix) { + this.knownPackagePrefixes.remove(prefix); + } + + /** + * 返回此 StandardTypeLocator 注册的所有导入前缀的列表。 + * @return 注册的导入前缀列表 + */ + public List getImportPrefixes() { + return Collections.unmodifiableList(this.knownPackagePrefixes); + } + + /** + * 查找(可能是未限定的)类型引用 - 首先使用原始类型名称,然后如果找不到类型名称,则尝试任何注册的前缀。 + * @param typeName 要定位的类型 + * @return 类型的 Class 对象 + * @throws EvaluationException 如果找不到类型 + */ + @Override + public Class findType(String typeName) throws EvaluationException { + String nameToLookup = typeName; + try { + return ClassUtils.forName(nameToLookup, this.classLoader); + } + catch (ClassNotFoundException ey) { + // 在放弃之前尝试任何已注册的前缀 + } + for (String prefix : this.knownPackagePrefixes) { + try { + nameToLookup = prefix + '.' + typeName; + return ClassUtils.forName(nameToLookup, this.classLoader); + } + catch (ClassNotFoundException ex) { + // 可能是另一个前缀 + } + } + throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); + } +} +``` + +### 六、主要实现 + +1. **StandardTypeLocator** + + + `StandardTypeLocator` 类是实现了 `TypeLocator` 接口的简单实现,用于在给定类型名称时定位类型信息。 + +### 七、最佳实践 + +使用 Spring 表达式语言(SpEL)来获取类型信息。通过解析不同的表达式,包括获取特定类型的 Class 对象和比较不同类型的枚举值,展示了 SpEL 在类型定位和类型比较方面的功能。 + +```java +public class TypeLocatorDemo { + public static void main(String[] args) { + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + // 解析表达式获取 Date 类的 Class 对象 + Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); + System.out.println("dateClass = " + dateClass); + + // 解析表达式获取 String 类的 Class 对象 + Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); + System.out.println("stringClass = " + stringClass); + + // 解析表达式比较两个 RoundingMode 枚举值的大小 + boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class); + System.out.println("trueValue = " + trueValue); + } +} +``` + +运行结果,成功获取了 `java.util.Date` 和 `java.lang.String` 的 Class 对象,并且对 `java.math.RoundingMode` 枚举类型进行了比较,结果为真。 + +```properties +dateClass = class java.util.Date +stringClass = class java.lang.String +trueValue = true +``` + +### 八、与其他组件的关系 + +1. **Class** + + + `Class` 类是 Java 反射中的重要类,用于表示类的运行时信息。它提供了获取类的名称、方法、字段等信息的方法。在 `TypeLocator` 接口的实现中,可能会使用 `Class` 类来表示获取到的类信息。 + +2. **ClassLoader** + + + `ClassLoader` 类是 Java 中的一个关键类,用于动态加载 Java 类文件到 Java 虚拟机中。它负责加载类文件并生成对应的 `Class` 对象。在与 `TypeLocator` 接口相关的实现中,可能会使用 `ClassLoader` 来加载和获取类信息。 + +3. **StandardTypeLocator** + + + `TypeLocator`接口的实现类,是一个简单的类型定位器,通常用于在 SpEL 中查找类型信息。 + +### 九、常见问题 + +1. **如何实现自定义的 TypeLocator?** + + - 我们可能会想要根据特定需求实现自定义的 `TypeLocator` 接口,以满足特定的类型定位需求。在这种情况下,需要实现 `findType(String typeName)` 方法,根据给定的类型名称查找对应的类型信息,并根据需求处理类型的查找逻辑。 + +2. **如何处理不同的类型查找策略?** + + - 在某些情况下,可能需要根据不同的情况使用不同的类型查找策略。例如,可能需要根据不同的包前缀使用不同的类型查找逻辑,或者需要根据不同的条件动态切换类型查找策略。在这种情况下,我们需要考虑如何设计和实现灵活的类型查找策略。 + +3. **如何处理类型查找失败的情况?** + + - 当无法找到指定类型时,需要考虑如何处理类型查找失败的情况。可能的处理方式包括抛出异常、返回默认值或者尝试其他类型查找策略。我们需要根据具体情况选择合适的处理方式,并确保用户能够得到明确的反馈。 \ No newline at end of file diff --git a/spring-spel/spring-spel-typeLocator/pom.xml b/spring-spel/spring-spel-typeLocator/pom.xml new file mode 100644 index 0000000..81b21c4 --- /dev/null +++ b/spring-spel/spring-spel-typeLocator/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-spel + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-spel-typeLocator + + \ No newline at end of file diff --git a/spring-spel/spring-spel-typeLocator/src/main/java/com/xcs/spring/TypeLocatorDemo.java b/spring-spel/spring-spel-typeLocator/src/main/java/com/xcs/spring/TypeLocatorDemo.java new file mode 100644 index 0000000..31418d7 --- /dev/null +++ b/spring-spel/spring-spel-typeLocator/src/main/java/com/xcs/spring/TypeLocatorDemo.java @@ -0,0 +1,23 @@ +package com.xcs.spring; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +public class TypeLocatorDemo { + public static void main(String[] args) { + // 创建一个SpEL表达式解析器 + ExpressionParser parser = new SpelExpressionParser(); + + // 解析表达式获取 Date 类的 Class 对象 + Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); + System.out.println("dateClass = " + dateClass); + + // 解析表达式获取 String 类的 Class 对象 + Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); + System.out.println("stringClass = " + stringClass); + + // 解析表达式比较两个 RoundingMode 枚举值的大小 + boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class); + System.out.println("trueValue = " + trueValue); + } +}