diff --git a/spring-metadata/pom.xml b/spring-metadata/pom.xml index 37f3dcf..4efecd6 100644 --- a/spring-metadata/pom.xml +++ b/spring-metadata/pom.xml @@ -14,6 +14,7 @@ spring-metadata-metadataReader + spring-metadata-annotationMetadata \ No newline at end of file diff --git a/spring-metadata/spring-metadata-annotationMetadata/README.md b/spring-metadata/spring-metadata-annotationMetadata/README.md new file mode 100644 index 0000000..916b3b3 --- /dev/null +++ b/spring-metadata/spring-metadata-annotationMetadata/README.md @@ -0,0 +1,276 @@ +## AnnotationMetadata + +- [AnnotationMetadata](#annotationmetadata) + - [一、知识储备](#一知识储备) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、最佳实践](#六最佳实践) + - [ASM字节码技术](#asm字节码技术) + - [JAVA反射技术](#java反射技术) + - [七、与其他组件的关系](#七与其他组件的关系) + - [八、常见问题](#八常见问题) + + +### 一、知识储备 + +1. **注解** + + 了解什么是注解以及如何定义和使用它们是很重要的。`AnnotationMetadata` 用于处理类上的注解信息,因此我们需要了解如何编写自定义注解以及如何在类、方法或字段上应用标准和自定义注解。 +2. **反射** + + `AnnotationMetadata` 使用反射机制来分析类的注解信息,所以我们需要了解 Java 反射的基本原理,包括 `Class` 对象、`Method`、`Field`、`Annotation` 类等。 +3. **类路径扫描** + + 如果我们通过 `metadataReader.getAnnotationMetadata()` 方法来读取类的注解信息,我们需要了解如何进行类路径扫描和资源加载,以便正确定位和读取类的字节码。 + +### 二、基本描述 + +`AnnotationMetadata` 是 Spring Framework 中的一个接口,用于访问和分析与类、方法、字段等元素相关的注解信息。它提供了一种方便的方式,让我们可以在运行时动态地获取和操作类上的注解信息。 + +### 三、主要功能 + +1. **检查类上的注解**: + - 我们可以使用 `hasAnnotation(String annotationName)` 方法来检查是否存在指定名称的注解在类上。这对于条件化的配置非常有用,可以根据类上的注解来控制不同的行为。 +2. **获取注解属性值**: + - 使用 `getAnnotationAttributes(String annotationName)` 方法,我们可以获取指定注解的属性值。这允许我们动态配置类的行为,根据注解属性的值来决定不同的处理逻辑。 +3. **获取类上的所有注解**: + - `getAnnotations()` 方法返回类上所有的注解,以 `AnnotationAttributes` 对象的列表形式。这使我们能够全面了解类的注解情况。 +4. **获取元注解信息**: + - `getMetaAnnotationTypes(String annotationName)` 方法用于获取指定注解上的元注解的类型,这对于分析注解层次结构非常有帮助。 +5. **扫描带有指定注解的类**: + - `getAnnotatedMethods(String annotationName)` 方法允许我们扫描指定包下所有带有指定注解的类,获取它们的元数据信息。这可用于实现自定义的组件扫描或注解处理逻辑。 +6. **获取类的接口和超类信息**: + - 我们可以使用 `getInterfaceNames()` 方法获取类实现的接口的全限定类名,以及使用 `getSuperClassName()` 方法获取类的超类的全限定类名。 +7. **获取类的成员信息**: + - 通过 `getMethodMetadata()`、`getFieldMetadata()` 和 `getClassMetadata()` 方法,我们可以获取类中方法、字段和类本身的元数据信息,以便分析这些元素上的注解。 + +### 四、接口源码 + +`AnnotationMetadata` 接口提供了一组方法,用于在运行时分析和操作类的注解信息。它允许我们获取类上的注解、元注解、接口信息、超类信息以及方法上的注解信息,以便实现各种功能,如条件化配置、组件扫描和自定义注解处理。接口中的 `introspect` 方法用于创建 `AnnotationMetadata` 实例,以便在运行时分析类的注解信息。 + +```java +/** + * 该接口定义了对特定类的注解的抽象访问,以一种无需加载该类的形式。 + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Phillip Webb + * @author Sam Brannen + * @since 2.5 + * @see StandardAnnotationMetadata + * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() + * @see AnnotatedTypeMetadata + */ +public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { + /** + * 获取底层类上的所有 存在 的注解类型的完全限定类名。 + * @return 注解类型的名称 + */ + default Set getAnnotationTypes() { + return getAnnotations().stream() + .filter(MergedAnnotation::isDirectlyPresent) + .map(annotation -> annotation.getType().getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * 获取给定注解类型上底层类上 存在 的所有元注解类型的完全限定类名。 + * @param annotationName 要查找的元注解类型的完全限定类名 + * @return 元注解类型的名称集合,如果没有找到则返回空集合 + */ + default Set getMetaAnnotationTypes(String annotationName) { + MergedAnnotation annotation = getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent); + if (!annotation.isPresent()) { + return Collections.emptySet(); + } + return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS).stream() + .map(mergedAnnotation -> mergedAnnotation.getType().getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * 确定给定类型的注解是否 存在 在底层类上。 + * @param annotationName 要查找的注解类型的完全限定类名 + * @return 如果存在匹配的注解则返回 {@code true} + */ + default boolean hasAnnotation(String annotationName) { + return getAnnotations().isDirectlyPresent(annotationName); + } + + /** + * 确定底层类是否具有其本身带有给定类型的元注解的注解。 + * @param metaAnnotationName 要查找的元注解类型的完全限定类名 + * @return 如果存在匹配的元注解则返回 {@code true} + */ + default boolean hasMetaAnnotation(String metaAnnotationName) { + return getAnnotations().get(metaAnnotationName, + MergedAnnotation::isMetaPresent).isPresent(); + } + + /** + * 确定底层类是否具有任何方法带有给定注解类型(或带有元注解的)。 + * @param annotationName 要查找的注解类型的完全限定类名 + */ + default boolean hasAnnotatedMethods(String annotationName) { + return !getAnnotatedMethods(annotationName).isEmpty(); + } + + /** + * 检索带有给定注解类型(或带有元注解的)的所有方法的方法元数据。 + *

对于任何返回的方法,{@link MethodMetadata#isAnnotated} 将对给定的注解类型返回 {@code true}。 + * @param annotationName 要查找的注解类型的完全限定类名 + * @return 具有匹配注解的方法的{@link MethodMetadata}集合。如果没有方法匹配注解类型,则返回空集合。 + */ + Set getAnnotatedMethods(String annotationName); + + /** + * 使用标准反射创建一个新的 {@link AnnotationMetadata} 实例的工厂方法,用于给定的类。 + * @param type 要分析的类 + * @return 新的 {@link AnnotationMetadata} 实例 + * @since 5.2 + */ + static AnnotationMetadata introspect(Class type) { + return StandardAnnotationMetadata.from(type); + } +} +``` + +### 五、主要实现 + +1. **StandardAnnotationMetadata**: + - 这个实现依赖于标准的 Java 反射机制,它使用 Java 的 `java.lang.Class` 对象来分析和访问类的注解信息。 + - 通过这个实现,我们可以轻松地检查类上的注解、获取注解属性值以及执行其他与注解相关的操作。 +2. **SimpleAnnotationMetadata**: + - 是一个基于 ASM(字节码操作库)的实现,用于分析和访问类的注解信息。相比于 `StandardAnnotationMetadata`,它通常更轻量,不依赖于标准 Java 反射机制,而是直接解析类的字节码。 + +~~~mermaid +classDiagram + direction BT + + class ClassMetadata { + <> + } + + class AnnotatedTypeMetadata { + <> + } + + class AnnotationMetadata { + <> + } + + class StandardClassMetadata { + } + + class SimpleAnnotationMetadata { + } + + class StandardAnnotationMetadata { + } + + AnnotationMetadata ..|> ClassMetadata + AnnotationMetadata ..|> AnnotatedTypeMetadata + SimpleAnnotationMetadata --|> AnnotationMetadata + StandardClassMetadata --|> ClassMetadata + StandardAnnotationMetadata ..|> StandardClassMetadata + StandardAnnotationMetadata --|> AnnotationMetadata + +~~~ + +### 六、最佳实践 + +#### ASM字节码技术 + +我们使用 ASM 字节码技术创建了一个 `AnnotationMetadata` 对象,通过创建 `MetadataReaderFactory` 和 `MetadataReader` 对象,加载了名为 "`com.xcs.spring.bean.MyBean`" 的类的元数据,然后通过 `AnnotationMetadata` 检查是否被 `@Component` 注解标记,并获取注解属性值,最后输出相应的结果。这个过程允许在不实际加载类的情况下,动态地分析和操作类的注解信息。 + +```java +public class AnnotationMetadataDemoByASM { + + public static void main(String[] args) throws Exception { + // 创建 MetadataReaderFactory + SimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(); + // 获取 MetadataReader + MetadataReader metadataReader = readerFactory.getMetadataReader("com.xcs.spring.bean.MyBean"); + + // 获取 AnnotationMetadata + AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); + + System.out.println("AnnotationMetadata impl class is " + annotationMetadata.getClass()); + + // 检查 MyBean 类是否被 @Component 注解标记 + boolean isComponent = annotationMetadata.hasAnnotation(Component.class.getName()); + System.out.println("MyBean is a @Component: " + isComponent); + + // 获取 MyBean 类上的注解属性 + if (isComponent) { + Map annotationAttributes = annotationMetadata.getAnnotationAttributes(Component.class.getName()); + System.out.println("@Component value is " + annotationAttributes.get("value")); + } + } +} +``` + +运行结果发现,`AnnotationMetadata` 的实现类是 `SimpleAnnotationMetadata`,这是一个基于 ASM 的实现,不依赖标准的 Java 反射机制。 + +```java +AnnotationMetadata impl class is class org.springframework.core.type.classreading.SimpleAnnotationMetadata +MyBean is a @Component: true +@Component value is myBean +``` + +#### JAVA反射技术 + +使用 Java 反射技术创建 `AnnotationMetadata` 对象,通过调用 `AnnotationMetadata.introspect(MyBean.class)` 方法,加载了名为 `MyBean` 的类的元数据。然后通过 `AnnotationMetadata` 检查是否被 `@Component` 注解标记,并获取注解属性值,最后输出相应的结果。使用标准 Java 反射机制来实现这些功能,这是一种通用的方式,适用于大多数 Spring 应用程序。 + +```java +public class AnnotationMetadataDemoByReflection { + + public static void main(String[] args) throws Exception { + // 获取 AnnotationMetadata + AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(MyBean.class); + + System.out.println("AnnotationMetadata impl class is " + annotationMetadata.getClass()); + + // 检查 MyBean 类是否被 @Component 注解标记 + boolean isComponent = annotationMetadata.hasAnnotation(Component.class.getName()); + System.out.println("MyBean is a @Component: " + isComponent); + + // 获取 MyBean 类上的注解属性 + if (isComponent) { + Map annotationAttributes = annotationMetadata.getAnnotationAttributes(Component.class.getName()); + System.out.println("@Component value is " + annotationAttributes.get("value")); + } + } +} +``` + +运行结果发现,`AnnotationMetadata` 的实现类是 `StandardAnnotationMetadata`,这是一个基于 JAVA反射机制的实现的。 + +```java +AnnotationMetadata impl class is class org.springframework.core.type.StandardAnnotationMetadata +MyBean is a @Component: true +@Component value is myBean +``` + +### 七、与其他组件的关系 + +1. **组件扫描** + - `AnnotationMetadata` 与 `ClassPathBeanDefinitionScanner` 类相关。在组件扫描过程中,`ClassPathBeanDefinitionScanner` 使用 `AnnotationMetadata` 来检查类上的特定注解(如 `@Component`、`@Service`、`@Controller` 等),并将这些类注册为 Spring Beans。 +2. **Bean 定义** + - `AnnotationMetadata` 与 `AnnotatedGenericBeanDefinition`,`ScannedGenericBeanDefinition` 类相关。当 Spring 扫描到带有注解的类时,会使用 `AnnotationMetadata` 来构建 Bean 定义。它提取类上的注解信息,包括 Bean 名称、作用域、依赖关系等,然后将这些信息用于创建 Bean 定义。 +3. **条件化的 Bean 注册** + - `AnnotationMetadata` 与条件化 Bean 注册相关的类有 `Conditional` 注解和 `Condition` 接口。在条件化注册中,`AnnotationMetadata` 用于评估 `@Conditional` 注解,根据条件的计算结果来决定是否注册特定的 Bean。 + +### 八、常见问题 + +1. **如何获取类上的特定注解信息?** + - 使用 `AnnotationMetadata` 的 `getAnnotations()` 方法,然后可以过滤和检查每个注解以获取特定注解的信息。例如,使用 `isDirectlyPresent()` 来检查注解是否直接存在于类上,然后使用 `getAnnotationAttributes()` 获取注解的属性值。 +2. **如何判断类是否被特定注解标记?** + - 使用 `AnnotationMetadata` 的 `hasAnnotation()` 方法,提供注解的完全限定名,可以检查类是否被特定注解标记。 +3. **如何处理条件化注册的问题?** + - 当使用条件化注解(如 `@Conditional`)时,需要使用 `AnnotationMetadata` 评估条件,并根据条件的结果来决定是否注册 Bean。通常,这需要自定义条件类和实现 `Condition` 接口来处理条件逻辑。 +4. **如何扫描和分析多个类?** + - 可以使用 Spring 的组件扫描功能,通过配置 `@ComponentScan` 注解或 XML 配置文件来扫描多个类。然后,`AnnotationMetadata` 可以用于分析扫描到的每个类。 +5. **如何自定义注解处理器?** + - 如果需要自定义处理特定注解的逻辑,可以编写自定义注解处理器,并使用 `AnnotationMetadata` 来分析和处理注解信息。这通常涉及实现自定义逻辑,例如动态创建 Bean 定义或执行其他操作。 +6. **如何选择使用 ASM 或标准 Java 反射?** + - 当选择使用 `AnnotationMetadata` 时,需要根据具体需求和性能考虑选择使用 ASM 或标准 Java 反射的实现。ASM 更适合需要高性能的场景,而标准 Java 反射通常更易于使用和维护。选择取决于项目的要求和性能需求。 \ No newline at end of file diff --git a/spring-metadata/spring-metadata-annotationMetadata/pom.xml b/spring-metadata/spring-metadata-annotationMetadata/pom.xml new file mode 100644 index 0000000..9ebf863 --- /dev/null +++ b/spring-metadata/spring-metadata-annotationMetadata/pom.xml @@ -0,0 +1,14 @@ + + + + spring-metadata + com.xcs.spring + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-metadata-annotationMetadata + + \ No newline at end of file diff --git a/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByASM.java b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByASM.java new file mode 100644 index 0000000..584078c --- /dev/null +++ b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByASM.java @@ -0,0 +1,39 @@ +package com.xcs.spring; + +import com.xcs.spring.bean.MyBean; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; + +/** + * @author xcs + * @date 2023年10月31日 16时17分 + **/ +public class AnnotationMetadataDemoByASM { + + public static void main(String[] args) throws Exception { + // 创建 MetadataReaderFactory + SimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(); + // 获取 MetadataReader + MetadataReader metadataReader = readerFactory.getMetadataReader("com.xcs.spring.bean.MyBean"); + + // 获取 AnnotationMetadata + AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); + + System.out.println("AnnotationMetadata impl class is " + annotationMetadata.getClass()); + + // 检查 MyBean 类是否被 @Component 注解标记 + boolean isComponent = annotationMetadata.hasAnnotation(Component.class.getName()); + System.out.println("MyBean is a @Component: " + isComponent); + + // 获取 MyBean 类上的注解属性 + if (isComponent) { + Map annotationAttributes = annotationMetadata.getAnnotationAttributes(Component.class.getName()); + System.out.println("@Component value is " + annotationAttributes.get("value")); + } + } +} diff --git a/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByReflection.java b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByReflection.java new file mode 100644 index 0000000..9acbcb1 --- /dev/null +++ b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/AnnotationMetadataDemoByReflection.java @@ -0,0 +1,31 @@ +package com.xcs.spring; + +import com.xcs.spring.bean.MyBean; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author xcs + * @date 2023年10月31日 16时17分 + **/ +public class AnnotationMetadataDemoByReflection { + + public static void main(String[] args) throws Exception { + // 获取 AnnotationMetadata + AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(MyBean.class); + + System.out.println("AnnotationMetadata impl class is " + annotationMetadata.getClass()); + + // 检查 MyBean 类是否被 @Component 注解标记 + boolean isComponent = annotationMetadata.hasAnnotation(Component.class.getName()); + System.out.println("MyBean is a @Component: " + isComponent); + + // 获取 MyBean 类上的注解属性 + if (isComponent) { + Map annotationAttributes = annotationMetadata.getAnnotationAttributes(Component.class.getName()); + System.out.println("@Component value is " + annotationAttributes.get("value")); + } + } +} diff --git a/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/bean/MyBean.java b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/bean/MyBean.java new file mode 100644 index 0000000..50ae9ba --- /dev/null +++ b/spring-metadata/spring-metadata-annotationMetadata/src/main/java/com/xcs/spring/bean/MyBean.java @@ -0,0 +1,11 @@ +package com.xcs.spring.bean; + +import org.springframework.stereotype.Component; + +/** + * @author xcs + * @date 2023年10月31日 16时21分 + **/ +@Component("myBean") +public class MyBean { +} diff --git a/spring-metadata/spring-metadata-metadataReader/README.md b/spring-metadata/spring-metadata-metadataReader/README.md index 6eee812..c0014ec 100644 --- a/spring-metadata/spring-metadata-metadataReader/README.md +++ b/spring-metadata/spring-metadata-metadataReader/README.md @@ -91,9 +91,6 @@ classDiagram } class SimpleMetadataReader { - -ClassLoader classLoader - -Set protocolResolvers - -Map, Map> resourceCaches } SimpleMetadataReader ..|> MetadataReader