Spring 类路径下 Bean 扫描实现分析
要获取类的信息,我们第一反应一般是通过反射获取,因为类加载器将类加载到虚拟机中,会读取 class 文件,而 class 文件中包含着完整的类的信息。那么 Spring 是怎么做的呢?是否还有其他更高效的方式?跟随源码,让我们一探究竟。
前言
接上篇 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 扫描完成。
更多推荐
所有评论(0)