浅聊类路径下 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);
    }
}

查找候选组件根据是否存在索引文件进行了不同的处理,跟踪 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 使用 asm 框架直接对类的信息进行读取,并将读取到的信息映射为 Spring 中表示元数据的 ClassMetadata 及 AnnotationMetadata 。拿到所有给定包名下的类的元信息后,通过检查类上是否存在支持的注解,如 Component,然后继续判断类上是否存在 @Conditional 注解及是否满足条件,最后还要保证类能够创建才能成为 Spring 中组件的候选对象。至此,Spring 的 bean 扫描完成。

 

参考:

 

posted @ 2022-01-13 17:29  残城碎梦  阅读(262)  评论(0编辑  收藏  举报