死磕Spring之IoC篇 - BeanDefinition 的解析过程(面向注解) (5)

2,componentsIndex 不为空,也就是说是通过 META-INF/spring.components 文件配置的 Bean,并且定义 Bean 的注解必须标注 @Index 注解,则调用 addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) 方法进行解析

扫描包路径,通过 ASM(Java 字节码的操作和分析框架)解析出符合条件的 AnnotatedGenericBeanDefinition 们,并返回。针对 1 解析过程中去扫描指定路径下的 .class 文件的性能问题,从 Spring 5.0 开始新增了一个 @Index 注解(新特性),@Component 注解上面就添加了 @Index 注解;这里不会去扫描指定路径下的 .class 文件,而是读取所有 META-INF/spring.components 文件中符合条件的类名,直接添加 .class 后缀就是编译文件,而不要去扫描,提高性能。

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java Class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

Spring 在很多地方都使用到了 ASM

4. scanCandidateComponents 方法

scanCandidateComponents(String basePackage) 方法,解析出包路径下所有符合条件的 BeanDefinition,方法如下:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) { // <1> 定义 `candidates` 用于保存符合条件的 BeanDefinition Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // <2> 根据包名生成一个扫描的路径,例如 `classpath*:包路径/**/*.class` String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + 'http://www.likecs.com/' + this.resourcePattern; // <3> 扫描到包路径下所有的 .class 文件 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); // <4> 开始对第 `3` 步扫描到的所有 .class 文件(需可读)进行处理,符合条件的类名会解析出一个 ScannedGenericBeanDefinition for (Resource resource : resources) { if (resource.isReadable()) { // 文件资源可读 try { // <4.1> 根据这个类名找到 `.class` 文件,通过 ASM(Java 字节码操作和分析框架)获取这个类的所有信息 // `metadataReader` 对象中包含 ClassMetadata 类元信息和 AnnotationMetadata 注解元信息 // 也就是说根据 `.class` 文件就获取到了这个类的元信息,而不是在 JVM 运行时通过 Class 对象进行操作,提高性能 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // <4.2> 根据所有的过滤器判断这个类是否符合条件(例如必须标注 @Component 注解或其派生注解) if (isCandidateComponent(metadataReader)) { // <4.3> 如果符合条件,则创建一个 ScannedGenericBeanDefinition 对象 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); // 来源和源对象都是这个 .class 文件资源 sbd.setResource(resource); sbd.setSource(resource); /* * <4.4> 再次判断这个类是否符合条件(不是内部类并且是一个具体类) * 具体类:不是接口也不是抽象类,如果是抽象类则需要带有 @Lookup 注解 */ if (isCandidateComponent(sbd)) { // <4.5> 符合条件,则添加至 `candidates` 集合 candidates.add(sbd); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } // <5> 返回 `candidates` 集合 return candidates; }

过程如下:

定义 candidates 用于保存符合条件的 BeanDefinition

根据包名生成一个扫描的路径,例如 classpath*:包路径/**/*.class

扫描到包路径下所有的 .class 文件

开始对第 3 步扫描到的所有 .class 文件(需可读)进行处理,符合条件的类名会解析出一个 ScannedGenericBeanDefinition

根据这个类名找到 .class 文件,通过 ASM(Java 字节码操作和分析框架)获取这个类的所有信息,生成 metadataReader 对象。这个对象其中包含 ClassMetadata 类元信息和 AnnotationMetadata 注解元信息,也就是说根据 .class 文件就获取到了这个类的元信息,而不是在 JVM 运行时通过 Class 对象进行操作,提高性能

根据所有的过滤器判断这个类是否符合条件(例如必须标注 @Component 注解或其派生注解)

如果符合条件,则创建一个 ScannedGenericBeanDefinition 对象,来源和源对象都是这个 .class 文件资源

再次判断这个类是否符合条件(不是内部类并且是一个具体类),具体类:不是接口也不是抽象类,如果是抽象类则需要带有 @Lookup 注解

符合条件,则添加至 candidates 集合

返回 candidates 集合

关于 ASM 的实现本文不进行探讨,感兴趣的可自行研究

4. addCandidateComponentsFromIndex 方法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywzdd.html