该系列文章是本人在学习 Spring 的过程当中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合个人源码注释 Spring 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.1.14.RELEASEjava
开始阅读这一系列文章以前,建议先查看《深刻了解 Spring IoC(面试题)》这一篇文章git
该系列其余文章请查看:《死磕 Spring 之 IoC 篇 - 文章导读》github
前面的几篇文章对 Spring 解析 XML 文件生成 BeanDefinition 并注册的过程进行了较为详细的分析,这种定义 Bean 的方式是面向资源(XML)的方式。面向注解定义 Bean 的方式 Spring 的处理过程又是如何进行的?本文将会分析 Spring 是如何将 @Component 注解或其派生注解
标注的 Class 类解析成 BeanDefinition(Bean 的“前身”)并注册。面试
在上一篇 《解析自定义标签(XML 文件)》文章中提到了处理 <context:component-scan />
标签的过程当中,底层借助于 ClassPathBeanDefinitionScanner 扫描器,去扫描指定路径下符合条件的 BeanDefinition 们,这个类就是处理 @Component 注解定义 Bean 的底层实现。关于 @ComponentScan 注解的原理也是基于这个扫描器来实现的,咱们先来看看这个扫描器的处理过程。spring
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
,继承 ClassPathScanningCandidateComponentProvider,classpath 下 BeanDefinition 的扫描器,支持设置过滤器缓存
默认有三个过滤器: @Component
注解的过滤器,Java EE 6 的 javax.annotation.ManagedBean
注解过滤器,JSR-330 的 javax.inject.Named
注解过滤器,这里咱们重点关注第一个过滤器app
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { /** BeanDefinition 注册中心 DefaultListableBeanFactory */ private final BeanDefinitionRegistry registry; /** BeanDefinition 的默认配置 */ private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults(); @Nullable private String[] autowireCandidatePatterns; /** Bean 的名称生成器 */ private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); /** 是否注册几个关于注解的 PostProcessor 处理器 */ private boolean includeAnnotationConfig = true; public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { this(registry, true); } public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) { this(registry, useDefaultFilters, getOrCreateEnvironment(registry)); } public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment) { this(registry, useDefaultFilters, environment, (registry instanceof ResourceLoader ? (ResourceLoader) registry : null)); } public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; if (useDefaultFilters) { // 注册默认的过滤器,@Component 注解的过滤器(具备层次性) registerDefaultFilters(); } setEnvironment(environment); // 设置资源加载对象,会尝试加载出 CandidateComponentsIndex 对象(保存 `META-INF/spring.components` 文件中的内容,不存在该对象为 `null`) setResourceLoader(resourceLoader); } }
属性很少,构造函数都会进入最下面这个构造方法,主要调用了两个方法,以下:框架
调用父类的 registerDefaultFilters()
方法,注册几个默认的过滤器,方法以下:ide
protected void registerDefaultFilters() { // 添加 @Component 注解的过滤器(具备层次性),@Component 的派生注解都符合条件 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }
添加 @Component
注解的过滤器(具备层次性),@Component
的派生注解都符合条件
也会添加 Java EE 6 的 javax.annotation.ManagedBean
注解过滤器,JSR-330 的 javax.inject.Named
注解过滤器
调用父类的 setResourceLoader(@Nullable ResourceLoader resourceLoader)
方法,设置资源加载对象并尝试加载出 CandidateComponentsIndex 对象,方法以下:
@Override public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); // 获取全部 `META-INF/spring.components` 文件中的内容 this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader()); }
这里有个关键的步骤,加载出 CandidateComponentsIndex 对象,尝试去获取全部 META-INF/spring.components
文件中的内容,后续进行分析
scan(String... basePackages)
方法,扫描出包路径下符合条件 BeanDefinition 并注册,方法以下:
public int scan(String... basePackages) { // <1> 获取扫描前的 BeanDefinition 数量 int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); // <2> 进行扫描,将过滤出来的全部的 .class 文件生成对应的 BeanDefinition 并注册 doScan(basePackages); // Register annotation config processors, if necessary. // <3> 若是 `includeAnnotationConfig` 为 `true`(默认),则注册几个关于注解的 PostProcessor 处理器(关键) // 在其余地方也会注册,内部会进行判断,已注册的处理器不会再注册 if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } // <4> 返回本次扫描注册的 BeanDefinition 数量 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
过程以下:
获取扫描前的 BeanDefinition 数量
进行扫描,将过滤出来的全部的 .class 文件生成对应的 BeanDefinition 并注册,调用 doScan(String... basePackages)
方法
若是 includeAnnotationConfig
为 true
(默认),则注册几个关于注解的 PostProcessor 处理器(关键),在其余地方也会注册,内部会进行判断,已注册的处理器不会再注册,记住这个 AnnotationConfigUtils 类
返回本次扫描注册的 BeanDefinition 数量
doScan(String... basePackages)
方法,扫描出包路径下符合条件 BeanDefinition 并注册,方法以下:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); // <1> 定义个 Set 集合 `beanDefinitions`,用于保存本次扫描成功注册的 BeanDefinition 们 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 遍历须要扫描的包名 // <2> 【核心】扫描包路径,经过 ASM(Java 字节码的操做和分析框架)解析出全部符合条件的 BeanDefinition Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // <3> 对第 `2` 步解析出来的 BeanDefinition 依次处理,并注册 for (BeanDefinition candidate : candidates) { // <3.1> 解析出 @Scope 注解的元信息并设置 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); // <3.2> 获取或者生成一个的名称 `beanName` String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // <3.3> 设置相关属性的默认值 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } // <3.4> 根据这个类的相关注解设置属性值(存在则会覆盖默认值) if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // <3.5> 检查 beanName 是否已存在,已存在可是不兼容则会抛出异常 if (checkCandidate(beanName, candidate)) { // <3.6> 将 BeanDefinition 封装成 BeanDefinitionHolder 对象,这里多了一个 `beanName` BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // <3.7> 若是代理模式是 `TARGET_CLASS`,则再建立一个 BeanDefinition 代理对象(从新设置了相关属性),原始 BeanDefinition 已注册 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // <3.8> 添加至 `beanDefinitions` 集合 beanDefinitions.add(definitionHolder); // <3.9> 注册该 BeanDefinition registerBeanDefinition(definitionHolder, this.registry); } } } // <4> 返回 `beanDefinitions`(已注册的 BeanDefinition 集合) return beanDefinitions; }
过程以下:
beanDefinitions
,用于保存本次扫描成功注册的 BeanDefinition 们findCandidateComponents(String basePackage)
方法2
步解析出来的 BeanDefinition 依次处理,并注册
beanName
beanName
是否已存在,已存在可是不兼容则会抛出异常beanName
TARGET_CLASS
,则再建立一个 BeanDefinition 代理对象(从新设置了相关属性),原始 BeanDefinition 已注册beanDefinitions
集合beanDefinitions
(已注册的 BeanDefinition 集合)第 2
步是这个扫描过程的核心步骤,在父类 ClassPathScanningCandidateComponentProvider 中进行分析,接下来的处理过程不复杂,获取相关属性进行配置
第 7
步建立代理对象,和 AOP 相关,感兴趣的可自行查看
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
,classpath 下扫描符合条件的 BeanDefinition
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; private String resourcePattern = DEFAULT_RESOURCE_PATTERN; /** 包含过滤器 */ private final List<TypeFilter> includeFilters = new LinkedList<>(); /** 排除过滤器 */ private final List<TypeFilter> excludeFilters = new LinkedList<>(); @Nullable private Environment environment; /** {@link Condition} 注解计算器 */ @Nullable private ConditionEvaluator conditionEvaluator; /** 资源加载器,默认 PathMatchingResourcePatternResolver */ @Nullable private ResourcePatternResolver resourcePatternResolver; /** MetadataReader 工厂 */ @Nullable private MetadataReaderFactory metadataReaderFactory; /** 全部 `META-INF/spring.components` 文件的内容 */ @Nullable private CandidateComponentsIndex componentsIndex; protected ClassPathScanningCandidateComponentProvider() { } public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) { this(useDefaultFilters, new StandardEnvironment()); } public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) { if (useDefaultFilters) { registerDefaultFilters(); } setEnvironment(environment); setResourceLoader(null); } @Override public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); // 获取全部 `META-INF/spring.components` 文件中的内容 this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader()); } }
构造函数在上一小节的 ClassPathBeanDefinitionScanner 的构造函数中都已经讲过了
咱们来看到 componentsIndex
属性,调用 CandidateComponentsIndexLoader#loadIndex(@Nullable ClassLoader classLoader)
方法生成的
org.springframework.context.index.CandidateComponentsIndexLoader
,CandidateComponentsIndexLoader 的加载器,代码以下:
public final class CandidateComponentsIndexLoader { public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components"; public static final String IGNORE_INDEX = "spring.index.ignore"; private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX); private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class); /** CandidateComponentsIndex 的缓存,与 ClassLoader 对应 */ private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache = new ConcurrentReferenceHashMap<>(); private CandidateComponentsIndexLoader() { } @Nullable public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader(); } // 获取全部 `META-INF/spring.components` 文件中的内容 return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex); } @Nullable private static CandidateComponentsIndex doLoadIndex(ClassLoader classLoader) { // 是否忽略 Index 的提高,经过配置 `spring.index.ignore` 变量,默认为 `false` if (shouldIgnoreIndex) { return null; } try { // 获取全部的 `META-INF/spring.components` 文件 Enumeration<URL> urls = classLoader.getResources(COMPONENTS_RESOURCE_LOCATION); if (!urls.hasMoreElements()) { return null; } // 加载全部 `META-INF/spring.components` 文件的内容 List<Properties> result = new ArrayList<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); result.add(properties); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + result.size() + "] index(es)"); } // 总共配置多少个 component 组件 int totalCount = result.stream().mapToInt(Properties::size).sum(); // 若是配置了 component 组件,则封装成 CandidateComponentsIndex 对象并返回 return (totalCount > 0 ? new CandidateComponentsIndex(result) : null); } catch (IOException ex) { throw new IllegalStateException("Unable to load indexes from location [" + COMPONENTS_RESOURCE_LOCATION + "]", ex); } } }
CandidateComponentsIndexLoader 被 final
修饰,也不容许实例化,提供 loadIndex(@Nullable ClassLoader classLoader)
静态方法,获取全部 META-INF/spring.components
文件中的内容,存在文件并包含内容则建立对应的 CandidateComponentsIndex 对象
整过过程不复杂,以下:
spring.index.ignore
变量判断是否须要忽略本次加载过程,默认为 false
META-INF/spring.components
文件META-INF/spring.components
文件的内容,并生成多个 key-value例如 META-INF/spring.components
文件这样配置:
example.scannable.AutowiredQualifierFooService=example.scannable.FooService example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component example.scannable.NamedComponent=org.springframework.stereotype.Component example.scannable.FooService=example.scannable.FooService example.scannable.FooServiceImpl=org.springframework.stereotype.Component,example.scannable.FooService example.scannable.ScopedProxyTestBean=example.scannable.FooService example.scannable.StubFooDao=org.springframework.stereotype.Component example.scannable.NamedStubDao=org.springframework.stereotype.Component example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component example.scannable.sub.BarComponent=org.springframework.stereotype.Component
生成的 CandidateComponentsIndex 对象以下所示:
findCandidateComponents(String basePackage)
方法,解析出包路径下全部符合条件的 BeanDefinition,方法以下:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { /* * 2. 扫描包路径,经过 ASM(Java 字节码的操做和分析框架)解析出符合条件的 AnnotatedGenericBeanDefinition 们,并返回 * 说明: * 针对 `1` 解析过程当中去扫描指定路径下的 .class 文件的性能问题,从 Spring 5.0 开始新增了一个 @Indexed 注解(新特性), * @Component 注解上面就添加了 @Indexed 注解 * * 这里不会去扫描指定路径下的 .class 文件,而是读取全部 `META-INF/spring.components` 文件中符合条件的类名, * 直接添加 .class 后缀就是编译文件,而不要去扫描 * * 没在哪看见这样使用过,能够参考 ClassPathScanningCandidateComponentProviderTest#customAnnotationTypeIncludeFilterWithIndex 测试方法 */ if (this.componentsIndex != null // `componentsIndex` 不为空,存在 `META-INF/spring.components` 文件而且解析出数据则会建立 && indexSupportsIncludeFilters()) // `includeFilter` 过滤器的元素(注解或类)必须标注 @Indexed 注解 { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { /* * 1. 扫描包路径,经过 ASM(Java 字节码的操做和分析框架)解析出符合条件的 ScannedGenericBeanDefinition 们,并返回 * 首先须要去扫描指定路径下全部的 .class 文件,该过程对于性能有很多的损耗 * 而后经过 ASM 根据 .class 文件能够获取到这个类的全部元信息,也就能够解析出对应的 BeanDefinition 对象 */ return scanCandidateComponents(basePackage); } }
这个方法的实现有两种方式,都是基于 ASM(Java 字节码的操做和分析框架)实现的,默认状况下都是第 1
种,分别以下:
1
,调用 scanCandidateComponents(String basePackage)
方法,默认
扫描包路径,经过 ASM(Java 字节码的操做和分析框架)解析出符合条件的 ScannedGenericBeanDefinition 们,并返回。首先须要去扫描指定路径下全部的 .class 文件,该过程对于性能有很多的损耗;而后经过 ASM 根据 .class 文件能够获取到这个类的全部元信息,也就能够解析出对应的 BeanDefinition 对象
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
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) + '/' + 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
用于保存符合条件的 BeanDefinitionclasspath*:包路径/**/*.class
3
步扫描到的全部 .class 文件(需可读)进行处理,符合条件的类名会解析出一个 ScannedGenericBeanDefinition
.class
文件,经过 ASM(Java 字节码操做和分析框架)获取这个类的全部信息,生成 metadataReader
对象。这个对象其中包含 ClassMetadata 类元信息和 AnnotationMetadata 注解元信息,也就是说根据 .class
文件就获取到了这个类的元信息,而不是在 JVM 运行时经过 Class 对象进行操做,提升性能candidates
集合candidates
集合关于 ASM 的实现本文不进行探讨,感兴趣的可自行研究
addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage)
方法,根据 META-INF/spring.components
文件,获取带有 @Indexed 注解的类名,而后解析出符合条件的 BeanDefinition,方法以下:
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) { // <1> 定义 `candidates` 用于保存符合条件的 BeanDefinition Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { Set<String> types = new HashSet<>(); // <2> 根据过滤器从全部 `META-INF/spring.components` 文件中获取全部符合条件的**类名称** for (TypeFilter filter : this.includeFilters) { // <2.1> 获取过滤注解(或类)的名称(例如 `org.springframework.stereotype.Component`) String stereotype = extractStereotype(filter); if (stereotype == null) { throw new IllegalArgumentException("Failed to extract stereotype from " + filter); } // <2.2> 获取注解(或类)对应的条目,并过滤出 `basePackage` 包名下的条目(类的名称) types.addAll(index.getCandidateTypes(basePackage, stereotype)); } boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); // <3> 开始对第 `2` 步过滤出来类名进行处理,符合条件的类名会解析出一个 AnnotatedGenericBeanDefinition for (String type : types) { // <3.1> 根据这个类名找到 `.class` 文件,经过 ASM(Java 字节码操做和分析框架)获取这个类的全部信息 // `metadataReader` 对象中包含 ClassMetadata 类元信息和 AnnotationMetadata 注解元信息 // 也就是说根据 `.class` 文件就获取到了这个类的元信息,而不是在 JVM 运行时经过 Class 对象进行操做,提升性能 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type); // <3.2> 根据全部的过滤器判断这个类是否符合条件(例如必须标注 @Component 注解或其派生注解) if (isCandidateComponent(metadataReader)) { // <3.3> 若是符合条件,则建立一个 AnnotatedGenericBeanDefinition 对象 AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition( metadataReader.getAnnotationMetadata()); /* * <3.4> 再次判断这个类是否符合条件(不是内部类而且是一个具体类) * 具体类:不是接口也不是抽象类,若是是抽象类则须要带有 @Lookup 注解 */ if (isCandidateComponent(sbd)) { // <3.5> 符合条件,则添加至 `candidates` 集合 candidates.add(sbd); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } // <4> 返回 `candidates` 集合 return candidates; }
过程以下:
candidates
用于保存符合条件的 BeanDefinitionMETA-INF/spring.components
文件中获取全部符合条件的类名称
org.springframework.stereotype.Component
)basePackage
包名下的条目(类的名称)2
步过滤出来类名进行处理,符合条件的类名会解析出一个 AnnotatedGenericBeanDefinition
.class
文件,经过 ASM(Java 字节码操做和分析框架)获取这个类的全部信息,生成 metadataReader
对象。这个对象其中包含 ClassMetadata 类元信息和 AnnotationMetadata 注解元信息,也就是说根据 .class
文件就获取到了这个类的元信息,而不是在 JVM 运行时经过 Class 对象进行操做,提升性能candidates
集合candidates
集合该过程不会去扫描到全部的 .class 文件,而是从 META-INF/spring.components
文件中读取,知道了类名称也就知道了 .class 文件的路径,而后能够经过 ASM 进行操做了。Spring 5.0 开始新增的一个 @Indexed 注解(新特性),目的为了提升性能。
本文面向注解(@Component 注解或其派生注解
)定义的 Bean,Spring 是如何将他们解析成 BeanDefinition(Bean 的“前身”)并注册的,大体过程以下:
@Component 注解或其派生注解
),符合条件则根据这个类的元信息生成一个 BeanDefinition 进行注册关于上面的第 1
步性能损耗很多,Spring 5.0 开始新增的一个 @Indexed 注解(新特性),@Indexed 派生注解(例如 @Component)或 @Indexed 注解的类能够定义在 META-INF/spring.components
文件中,Spring 会直接从文件中读取,找到符合条件的类名称,也就找到了 .class 文件。这样一来对于上面第 1
步来讲在性能上获得了提高,目前还没见到这种方式,毕竟 还要再文件中定义类名,感受太复杂了,启动过程慢就慢点😄
到这里,对于经过 面向资源(XML、Properties)、面向注解 两种定义 Bean 的方式,Spring 将定义的信息转换成 BeanDefinition(Bean 的“前身”)的过程差很少都分析了。咱们接下来研究一下 Bean 的生命周期,BeanDefinition 是如何变成 Bean 的。
本文你是否还有疑惑,@Bean 注解定义的 Bean 怎么没有解析成 BeanDefinition 呢?别急,在后续的文章会进行分析