不管是传统SSM应用中使用XML的配置方式(context命名空间中的component-scan标签),还是Spring Boot中的注解配置方式(@ComponentScan),一般都会使用包扫描,也就是扫描某个包下的所有组件bean。本文就来看看是怎么扫描的,又是怎么注册到bean工厂中的。
doScan方法
有的地方会调用到scan方法,而有的地方会直接调用doScan方法,在scan方法中也是会调用到doScan方法。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // 保存扫描到的bean, 扫描完后会返回
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // 查找满足要求的bean并生成beanDefinition
for (BeanDefinition candidate : candidates) { // 遍历beanDefinition
// 获取bean的作用域元数据,与@Scope注解相关
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// 设置bean的作用域
candidate.setScope(scopeMetadata.getScopeName());
// 生成bean的名称,与@Component等注解相关
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 一般这里beanDefinition的类型是ScannedGenericBeanDefinition,间接继承了AbstractBeanDefinition,所以为true
if (candidate instanceof AbstractBeanDefinition) {
// 为beanDefinition设置一些默认属性
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 一般这里beanDefinition的类型是ScannedGenericBeanDefinition,实现了AnnotatedBeanDefinition接口,所以为true
if (candidate instanceof AnnotatedBeanDefinition) {
// 处理@Lazy、@DependsOn、@Role、@Description注解
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) { // 如果不存在同名bean
// 将beanDefinition封装成beanDefinitionHolder
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 根据@Scope注解中的值为beanDefinition设置相应的代理模式,后续在处理AOP相关问题时会用到
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 添加扫描到的bean定义
beanDefinitions.add(definitionHolder);
// 把bean定义注册进工厂
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
public int scan(String... basePackages) {
// 获取扫描前的beanDefinition数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 进行扫描,将过滤出来的所有class文件生成对应的beanDefinition并注册
doScan(basePackages);
// Register annotation config processors, if necessary.
// 这里这个属性默认就是true
if (this.includeAnnotationConfig) {
/*
* 注册(那6个默认的)注解配置处理器,
* 其实在AnnotatedBeanDefinitionReader的构造方法中就调用过该方法了。
* 当然这里的重复注册也没有关系,内部会判断是不是已经存在了,不存在才会注册。
*/
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 返回本次扫描注册的beanDefinition的数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
在doScan方法中,主要做了下面几件事:
- 查找满足要求的bean并生成bean定义;
- 遍历查找结果:
- 解析作用域元数据;
- 设置默认属性;
- 处理一些注解;
- 注册bean;
扫描bean
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// Spring5新增的一种扫描方式,用了索引,启动速度快,适合大型项目(这种方式不做重点分析)
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else { // 普通扫描方式(大多数都是这种情况)
return scanCandidateComponents(basePackage);
}
}
这里分为了两种扫描方式:
- 使用索引的扫描方式;
- 普通扫描方式;
普通扫描
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
/*
* 根据包名生成扫描路径:
* 其实包名中可以存在spEL表达式,resolveBasePackage会处理
*/
// 解析后的路径形如:net.lzip.ant ----> classpath*:net/lzip/ant/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
/*
* 会根据扫描路径获取到包下所有.class文件,并以resource对象来表示,
* 这里之所以使用class文件而不是Class对象,是为了提高性能,
* Spring会使用ASM(Java字节码处理框架)来处理class文件而不是靠JVM来加载类对象
*/
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
// 遍历所有的resource对象
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
// 读取类的注解信息和类信息,并封装在MetadataReader中
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
/*
* 这里isCandidateComponent的逻辑是:
* 先用excludeFilter去匹配,如果匹配上了,会返回false;否则继续执行
* 再用includeFilter去匹配,如果匹配上了,会返回true;否则会返回false
*/
if (isCandidateComponent(metadataReader)) {
// 创建bean定义
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
/*
* 再次判断。注意,这里的isCandidateComponent是上面那个的重载方法
* 要求是具体类或抽象类,如果是抽象类则要求带有@Lookup注解
*/
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd); // 符合条件,则添加至candidate集合
}
}
else {
}
}
}
}
// 返回beanDefinition集合
return candidates;
}
在上面方法中主要做了下面几件事:
- 加载包下的所有class文件资源;
- 创建bean定义;
- 使用模板配置;
- 处理一些常见注解;
- 判断bean是否重复;
- 如果bean不重复,注册bean定义;
这里不过多涉及class文件的加载和解析,只需要知道Spring是使用了MetadataReader这个类来读取class文件的元数据,出于性能考虑,是基于一个叫作ASM的Java字节码处理框架来实现的,而没有使用JVM的类加载机制。
重点来看一下是怎么判断bean定义是不是属于候选bean的。
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())) {
// 进一步做条件匹配,会跳过哪些不满足条件的bean
return isConditionMatch(metadataReader);
}
}
return false;
}
这里使用了exclude和include过滤器来处理,首先是使用排除过滤器来排除bean,然后使用包含过滤器来判断bean是不是应该包含bean,最后还会检查bean是否满足条件要求。
正确理解过滤器的过滤效果,需要注意这里的先后顺序,是先使用exclude过滤器,再使用include过滤器。 对于没有被exclude过滤器排除,也有没有被include过滤器包含的那些bean,默认也是不会被注册的。对于include过滤器包含的,但是被exclude过滤器排除的,也不会被注册,因为exclude过滤器先执行,也可以理解为exclude过滤器的优先级更高一些。
private boolean isConditionMatch(MetadataReader metadataReader) {
if (this.conditionEvaluator == null) {
// 创建条件判断器
this.conditionEvaluator =
new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
}
// 执行条件判断
return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}
这里会使用条件判断器来处理条件注解(@Conditional),后面单独写文章来分析条件注解是怎么被处理的。
处理bean扫描结果
解析bean的作用域
private final ScopedProxyMode defaultProxyMode;
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;
// 解析bean的作用域
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
// 创建作用域元数据对象,默认的作用域名称是singleton
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
// 获取@Scope注解
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
// 将@Scope注解的value属性作为作用域名称设置到元数据对象中
metadata.setScopeName(attributes.getString("value"));
// 获取proxyMode字段的值
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
// 判断是不是@Scope注解的proxyMode字段的默认值
if (proxyMode == ScopedProxyMode.DEFAULT) {
// 在ClasspathBeanDefinitionScanner中创建该类实例时,调用的是无参构造方法,所以这里的默认值是ScopedProxyMode.NO
proxyMode = this.defaultProxyMode;
}
// 设置作用域代理模式
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
该方法中主要是在处理@Scope注解,获取注解的属性值并设置到bean定义对象中。
生成bean的名称
这里以AnnotationBeanNameGenerator为例,因为在ConfigurationClassPostProcessor是使用的是该类中的INSTANCE属性。下面来看看是怎么生成bean的名称的。
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// 从注解中获取bean的名称
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
// 获取到则直接返回
return beanName;
}
}
// Fallback: generate a unique default bean name.
// 构建bean的名称
return buildDefaultBeanName(definition, registry);
}
该方法先从注解信息中获取bean的名称,如果没有再生成。
获取bean的名称
public static final AnnotationBeanNameGenerator INSTANCE = new AnnotationBeanNameGenerator();
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();
@Nullable
protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
// 获取注解信息
AnnotationMetadata amd = annotatedDef.getMetadata();
// 获取注解集合
Set<String> types = amd.getAnnotationTypes();
String beanName = null;
// 遍历注解
for (String type : types) {
// 获取注解的属性
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
if (attributes != null) {
// 获取注解的元注解的名称
Set<String> metaTypes = this.metaAnnotationTypesCache.computeIfAbsent(type, key -> {
// 获取注解的元注解类型
Set<String> result = amd.getMetaAnnotationTypes(key);
return (result.isEmpty() ? Collections.emptySet() : result);
});
/*
* 判断注解是不是模板注解且属性不是null以及注解中包含value属性
* 这里一般都不会设置其value属性,比如开发中一般都是直接用@Controller、@Service等注解修饰类,而不会设置其value属性。
*/
if (isStereotypeWithNameValue(type, metaTypes, attributes)) {
// 获取注解的value字段的值
Object value = attributes.get("value");
if (value instanceof String) {
// 强转为字符串
String strVal = (String) value;
if (StringUtils.hasLength(strVal)) {
// 判断bean是不是存在多个名称
if (beanName != null && !strVal.equals(beanName)) {
// 如果是则抛出异常
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
"component names: '" + beanName + "' versus '" + strVal + "'");
}
beanName = strVal;
}
}
}
}
}
// 返回bean的名称
return beanName;
}
public static final AnnotationBeanNameGenerator INSTANCE = new AnnotationBeanNameGenerator();
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();
protected boolean isStereotypeWithNameValue(String annotationType,
Set<String> metaAnnotationTypes, @Nullable Map<String, Object> attributes) {
// 判断是不是模板注解
boolean isStereotype =
// 要么是@Component注解
annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
// 要么注解的元注解中包含@Component,比如@Controller、@Server、@Repository
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
// 还对这两个JarkataEE中的注解做了支持
annotationType.equals("javax.annotation.ManagedBean") ||
annotationType.equals("javax.inject.Named");
// 要求是模板注解、包含属性且属性中包含value字段
return (isStereotype && attributes != null && attributes.containsKey("value"));
}
其实这里主要就是在获取@Component注解的value属性的值,将其作为bean的名称。但一般使用时是没有为其设置value属性的。
生成bean的名称
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return buildDefaultBeanName(definition);
}
protected String buildDefaultBeanName(BeanDefinition definition) {
// 获取bean的类名称
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
// 获取bean的类型的短名称
String shortClassName = ClassUtils.getShortName(beanClassName);
// 将首字母变为小写
return Introspector.decapitalize(shortClassName);
}
这里可以看到,默认以类名作为bean的名称。
配置默认属性
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
// 应用BeanDefinition模板,可以通过模板来配置需要应用在多个bean定义中的自定义配置
beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
// 设置该bean是否是其他bean自动装配时的候选bean,
beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
}
}
在该方法中,为bean定义配置了模板定义中的配置。以及设置了是否作为自动装配的候选bean,但一般没有设置autowireCandidatePatterns属性,可以忽略。
处理常见的一些注解
public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
processCommonDefinitionAnnotations(abd, abd.getMetadata());
}
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
// 获取并处理@Lazy注解
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
// 处理@Primary注解
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
// 获取并处理@DependOn注解
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
// 获取并处理@Role注解
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
// 获取并处理@Description注解
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
该方法中就是获取类上的这些注解,然后将其属性值设置到bean定义中。
bean重复性检查
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
// 如果bean工厂中不存在该名称的bean,说明没有重复的bean,则直接返回true
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
// 获取已经存在的bean的定义
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// 判断能不能容忍出现同名bean
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
// 如果不能容忍则抛出异常
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}
protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
// 当前类扫描的bean定义都是ScannedGenericBeanDefinition类型的,其他地方注册的bean定义不抛出冲突异常,只是不会注册新bean而已。
return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
// 扫描了同一个类文件两次
(newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice
// 扫描了同一个类两次,这里区分类文件和类,或许是因为类文件中有可能存在多个类吧
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
}
如果扫描到重复bean,是不会注册新bean的,另外会判断能不能忽略出现重复bean的问题,如果不能则会抛出异常。
注册bean
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
// 注册bean定义
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
// 获取bean的名称
String beanName = definitionHolder.getBeanName();
// 注册beanDefinition
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
// 注册alias别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
// 注册别名
registry.registerAlias(beanName, alias);
}
}
}
最后,会将扫描到的bean定义注册到bean工厂中,这里同时注册了bean的名称和别名。
总结
本文详细介绍了ClassPathBeanDefinitionScanner的bean扫描过程,一般我们在项目中使用的@Controller、@Service、@Repository和@Configuration等注解修饰的类都会经过本文介绍的过程来处理。