Spring中的ClassPathBeanDefinitionScanner

不管是传统SSM应用中使用XML的配置方式(context命名空间中的component-scan标签),还是Spring Boot中的注解配置方式(@ComponentScan),一般都会使用包扫描,也就是扫描某个包下的所有组件bean。本文就来看看是怎么扫描的,又是怎么注册到bean工厂中的。


doScan方法

有的地方会调用到scan方法,而有的地方会直接调用doScan方法,在scan方法中也是会调用到doScan方法。

doScan
scan
<
>
java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
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;
}
java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
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

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // Spring5新增的一种扫描方式,用了索引,启动速度快,适合大型项目(这种方式不做重点分析)
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else { // 普通扫描方式(大多数都是这种情况)
        return scanCandidateComponents(basePackage);
    }
}

这里分为了两种扫描方式:

  • 使用索引的扫描方式;
  • 普通扫描方式;

普通扫描

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
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的。

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
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过滤器的优先级更高一些。

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
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的作用域

java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
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的名称的。

java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
@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的名称

determineBeanNameFromAnnotation
isStereotypeWithNameValue
<
>
java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
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;
}
java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
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的名称

java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
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的名称。

配置默认属性

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
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属性,可以忽略。

处理常见的一些注解

java
spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
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重复性检查

java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
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

ClassPathBeanDefinitionScanner
BeanDefinitionReaderUtils
<
>
java
spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
    // 注册bean定义
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
java
spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java
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等注解修饰的类都会经过本文介绍的过程来处理。