前言

接上篇 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 扫描完成。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐