前言
接上篇 Spring 5 启动性能优化之 @Indexed,上篇提到 Spring 可以在编译时生成索引文件,在应用上下文启动时可以通过索引文件查找所需要的注册的 Bean,如果不存在索引文件或者配置了不处理索引文件的参数,则不会从索引文件获取元数据。这时,Spring 便需要从指定的包中扫描 bean。
要获取类的信息,我们第一反应一般是通过反射获取,因为类加载器将类加载到虚拟机中,会读取 class 文件,而 class 文件中包含着完整的类的信息。那么 Spring 是怎么做的呢?是否还有其他更高效的方式?跟随源码,让我们一探究竟。
Spring Bean 扫描分析
要想成为 Spring 的 bean,我们通常会在类上添加注解 @Component、@Controller、@Service、@Repository 等注解。并且 Spring 从 4.x 版本开始 Spring 开始支持条件注解 @Conditional,满足条件的类才会注册为 Spring 的 bean 。因此在分析源码之前,我们可以大概猜到具有这么一个流程。首先 Spring 从类路径中读取到所有的类的元数据,然后选择存在特定注解的类,最后如果存在条件注解还需要满足条件才能注册为 bean。实际上是不是这样呢?
扫描流程概览
我们仍将 AnnotationConfigApplicationContext 应用上下文的扫描方法作为入口进行分析。源码如下:
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { // 用来注册 bean private final AnnotatedBeanDefinitionReader reader; // 用来扫描并注册 bean private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } //从给定的包中扫描 @Override public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); this.scanner.scan(basePackages); } }
scan 是扫描包的方法,实现异常简单,其委托给 ClassPathBeanDefinitionScanner 进行扫描,这是 Spring 内部实现经常用的一个组合模式,使类的职责更为清晰。查看 ClassPathBeanDefinitionScanner 扫描的源码如下:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); // 扫描并注册 bean doScan(basePackages); // 如果有必要的话,注册处理注解的 BeanFactoryPostProcessor if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } }
这里扫描分成了两个步骤,一个是扫描,另一个是注册处理注解配置的处理器 BeanFactoryPostProcessor ,应用上下文在其生命周期中会调用
BeanFactoryPostProcessor 的方法,这给予对 bean 进行额外处理的机会,如将 @Configuration 标注的类中标注 @Bean 注解的方法的返回对象注册为 Spring 中的 bean 。 注册的处理器及其实现在后面进行探讨,这里让我们把重点放到扫描过程。 跟踪 doScan 方法的源码如下:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 循环从包中查找候选组件 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); // 获取或生成 bean 的名称 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // 对候选组件进行再处理,设置必要的信息 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 检查候选组件是否与现有 bean冲突或需要注册 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册 bean registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
Spring 的方法一般不会太长,结构也较为清晰,这里我们可以看到,Spring 先查找候选组件,然后对候选组件设置必要的属性,最后检查候选组件与现存的 bean 定义如果无冲突,则进行注册,注册则是使用一个 map 来保存 bean 定义,后面再分析。跟踪其查找候选组件的方法 findCandidateComponents,源码如下:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } }
查找候选组件根据是否存在索引文件进行了不同的处理,参见前文 Spring 5 启动性能优化之 @Indexed 查看索引文件的处理过程,这里把重点放到候选组件的扫描过程。跟踪 scanCandidateComponents 方法源码如下,其中省略了不重要的代码:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); // 先将包名转换为 Resource String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); for (Resource resource : resources) { // 然后获取元数据读取器 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // 如果读取到的类为候选组件则添加到候选组件列表 if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { candidates.add(sbd); } } } return candidates; }
这里先将包名转换为 Resource,Resource 是 Spring 对资源的统一抽象,然后对 Resource 进行读取,转换为 MetadataReader ,MetadataReader 是元数据的读取器,可以读取到类的元信息,然后判断元信息对应的类为候选组件则添加到候选组件列表。后面我们关注两件事情,第一是 Resource 如何转换为 MetadataReader ,第二是如何判断类是否符合候选组件要求。
Resource 如何转换为 MetadataReader?
上述方法中先调用了 getMetadataReaderFactory() 获取 MetadataReaderFactory,然后使用 MetadataReaderFactory 获取 MetadataReader,那么我们就来看这几个类到底是做什么用的,包含了哪些信息。
查看 MetadataReaderFactory 源码如下:
public interface MetadataReaderFactory { // 根据类名获取 MetadataReader MetadataReader getMetadataReader(String className) throws IOException; // 根据 Resource 获取 MetadataReader MetadataReader getMetadataReader(Resource resource) throws IOException; }
MetadataReaderFactory 是一个接口,其内部有两个工厂方法都是用来获取 MetadataReader ,再看什么是 MedataReader,源码如下:
public interface MetadataReader { // 获取 Resource Resource getResource(); // 获取类的元数据 ClassMetadata getClassMetadata(); // 获取注解的元数据 AnnotationMetadata getAnnotationMetadata(); }
MetadataReader 提供了获取 ClassMetadata 和 AnnotationMetadata 对象的方法,那么这两个类都是做什么用的呢?继续跟踪源码。ClassMetadata 源码如下:
public interface ClassMetadata { // 获取类名 String getClassName(); // 当前类是否为接口 boolean isInterface(); // 当前类是否表示注解 boolean isAnnotation(); // 当前类是否是抽象的 boolean isAbstract(); // 当前类是否可以创建 default boolean isConcrete() { return !(isInterface() || isAbstract()); } // 当前类是否标记为 final boolean isFinal(); // 当前类是否是独立的,即为最顶部的类或为静态内部类 boolean isIndependent(); // 当前类是否定义在外部类中 default boolean hasEnclosingClass() { return (getEnclosingClassName() != null); } // 获取外部类的名称 @Nullable String getEnclosingClassName(); // 当前类是否具有父类 default boolean hasSuperClass() { return (getSuperClassName() != null); } // 获取当前类的父类的名称 @Nullable String getSuperClassName(); // 获取当前类实现的接口名称 String[] getInterfaceNames(); // 获取当前类的成员内部类名称 String[] getMemberClassNames(); }
可以看到,ClassMetadata 是获取类的元数据的一个接口,其上面的信息也可以通过反射获取。再看 AnnotationMetadata ,源码如下:
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { // 获取注解上的元注解类型名称 default Set<String> getMetaAnnotationTypes(String annotationName) { ...省略部分代码 } // 给定的注解是否存在于类上 default boolean hasAnnotation(String annotationName) { ...省略部分代码 } // 给定的元注解是否存在于类上 default boolean hasMetaAnnotation(String metaAnnotationName) { ...省略部分代码 } // 给定的注解是否存在于类上方法上 default boolean hasAnnotatedMethods(String annotationName) { ...省略部分代码 } // 获取所有被给定注解或元注解标注的方法元数据 Set<MethodMetadata> getAnnotatedMethods(String annotationName); // 使用反射创建给定类的注解元数据 static AnnotationMetadata introspect(Class<?> type) { return StandardAnnotationMetadata.from(type); }
可以看到,AnnotationMetadata 继承了 ClassMetadata ,并且包含一些有关注解的元数据。既然 MetadataReader 是使用 MetadataReaderFactory 获取到的,那么跟踪 getMetadataReaderFactory() 方法查看如何获取 MetadataReaderFactory 的实现。源码如下:
public final MetadataReaderFactory getMetadataReaderFactory() { if (this.metadataReaderFactory == null) { this.metadataReaderFactory = new CachingMetadataReaderFactory(); } return this.metadataReaderFactory; }
这里获取到的是 CachingMetadataReaderFactory 的实例,它会调用父类 SimpleMetadataReaderFactory 的 getMetadataReader 方法,然后把获取到的 MetaReader 缓存,再跟踪 SimpleMetadataReaderFactory 的 getMetadataReader 方法的实现,源码如下:
@Override public MetadataReader getMetadataReader(Resource resource) throws IOException { return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader()); }
这里看到实际上就是 new 了一个 SimpleMetadataReader,查看其构造方法源码如下:
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader); getClassReader(resource).accept(visitor, PARSING_OPTIONS); this.resource = resource; this.annotationMetadata = visitor.getMetadata(); } @Override public ClassMetadata getClassMetadata() { return this.annotationMetadata; } @Override public AnnotationMetadata getAnnotationMetadata() { return this.annotationMetadata; }
这里看到 SimpleMetadataReader 使用了 asm 框架中的 ClassReader 读取类的信息,将 SimpleAnnotationMetadataReadingVisitor 作为访问者,接收 asm 访问到的类元信息,然后再转换为 AnnotationMetadata 保存,这样就获取到了 MetadataReader 中保存的元数据。通过上面的流程我们发现,Spring 实际并没有使用反射,因此不要进行类加载,使用 asm 框架直接读取 class 信息,大大提高了性能。
如何判断一个类是否满足组件要求
读取到类的元数据后就需要判断类是否满足组件的要求,跟踪 ClassPathBeanDefinitionScanner 的 isCandidateComponent 方法,其方法为父类 ClassPathScanningCandidateComponentProvider 的方法,源码如下:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; }
这里先使用 excludeFilters 排序满足条件的类,默认情况下 excludeFilters 为空,不会对扫描到的类进行排除。
排除不需要的类后就需要判断扫描的类是否满足条件。先使用 includeFilters 进行判断,满足条件的类有可能成为 Spring 的 bean。默认的 includeFilter 在 ClassPathBeanDefinitionScanner 实例化时注册,源码如下:
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; if (useDefaultFilters) { // 注册默认的过滤器 registerDefaultFilters(); } setEnvironment(environment); setResourceLoader(resourceLoader); } protected void registerDefaultFilters() { ...省略部分代码 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); }
可以看到,默认注册了支持 @Component、@ManagedBean、@Named 注解的 TypeFilter,只有类上存在这三个注解中的一个才可能成为候选组件。在 Spring 4.x 版本中,Spring 添加了条件注解,这里进行了支持。满足 includeFilter 后还使用 isConditionMatch 判断了条件是否满足,继续跟踪源码:
private boolean isConditionMatch(MetadataReader metadataReader) { if (this.conditionEvaluator == null) { this.conditionEvaluator = new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver); } return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); }
这里使用 ConditionEvaluator 对注解 @Conditional 进行处理,不满足则会跳过后面的处理。后续会专门对 @Conditional 进行分析。isConditionMatch 还具有另外一个重载的方法,只有两个条件都满足才会真正成为 Spring 的bean。这两个方法调用都在 ClassPathScanningCandidateComponentProvider#scanCandidateComponents ,相关源码前面已经进行了分析,这里再贴一次避免嵌套过深大家找不到。isConditionMatch 重载方法的源码如下:
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); }
这里类成为 Spring 的 bean,还需要保证是独立能创建的。
整理流程如下:Spring 拿到扫描到的类的元信息,判断类上是否存在支持的注解,如果存在继续判断是否满足 @Conditional 注解,最后还要保证类是能够创建的,这样的一个类才完全的满足 Spring 组件的要求,成为 Spring 的 bean。
总结
本篇首先引出 Spring 在类路径下扫描 bean 的过程。通过分析,我们发现 Spring 使用 asm 框架直接对类的信息进行读取,并将读取到的信息映射为 Spring 中表示元数据的 ClassMetadata 及 AnnotationMetadata 。拿到所有给定包名下的类的元信息后,通过检查类上是否存在支持的注解,如 Component,然后继续判断类上是否存在 @Conditional 注解及是否满足条件,最后还要保证类能够创建才能成为 Spring 中组件的候选对象。至此,Spring 的 bean 扫描完成。