Spring源码分析之——从component-scan看注解的运用

前言

先做个基础知识普及,如何理解注解?

我的理解,注解就是扩展版的接口,接口的使用场景很有限,只能由类去实现接口,而注解则丰富的多,它可以用在类,实例属性,方法,参数等上面;

我对接口的理解是:接口的核心是用来表示一类事物的,比如:Person接口,凡是继承Person接口的都是人,Driver接口,凡是继承这个接口的都是司机。这种设计的作用就是业务逻辑(框架代码)可用通过类的这种继承关系来找到某一类对象。

注解大致延续和丰富了接口的这种先天的作用,一般情况下,注解被用作:标注一类事物(同接口一样);扩展一种功能;

1,标注一类事务

很好理解,比如Spring Bean,当一个类被@Component,@Service,@Repository等修饰的时候,它就表示是一个Spring Bean,在初始化的时候就会被Spring容器管理;

2,扩展一种功能

通常框架代码通过动态代理的形式增强被标注的类或者方法,比如:@Transactional,@Aspect,@Pointcut等;

 

原理

上面说的是注解的作用,但是注解是如何起作用的?这就好比定一个标准,得有人去执行标准,而执行标准的就是框架的源码,也就是本篇要研究的,标准定义被Service修饰就是一个Bean,框架代码在处理的时候当遇到类被Service注解修饰,就会把类封装成BeanDefinition进行注册;其他人随便写一个注解@MyService,加到类头上,Spring就不会把它当成Bean来管理。

 

配置

spirng配置文件,配置扫描路径:

<!-- 开启注解扫描 -->
<context:component-scan base-package="org.liuwei"></context:component-scan>

 这么一行配置,背后当然是有对应处理代码,代码就在命名空间句柄类中初始化

META-INF/spring.handler ——句柄配置文件,会把对应关系维护起来,在解析配置文件的时候方便找到对应的解析类

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

 

解析与扫描

Spring处理文件,通常是由解析类和扫描类组成,或者叫解析器和扫描器,解析器中获取扫描器,由扫描器负责扫描配置文件,得到数据后,解析器再做后续处理。

从上面句柄类可以看到 component-scan 对应的处理类:ComponentScanBeanDefinitionParser,处理注解Bean就是这样的套路,下面解析代码中先得到一个扫描器,调用扫描器doScan方法获取BeanDefinitionHolder集合,扫描器能够扫描出哪些是Bean,其中必定有扫描的过滤规则,可用猜想到规则就是那些注解(接口)。跟代码发现在构造扫描器对象的时候,就会把过滤规则生成。

org.springframework.context.annotation.ComponentScanBeanDefinitionParser

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 这个方法虽然返回类型是BeanDefinition,但最终返回的是null,
    // 因为扫描路径下肯定不止一个bean,即便要返回也应该是返回集合才对。
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    // 拆分配置的多个路径
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Actually scan for bean definitions and register them.
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

    // 这里获取beanDefinitions集合,那么判断逻辑肯定也在这个里面,我们重点看这里
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // 注册BeanDefinition到BeanFactory
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
}

 

扫描器,在器构造方法中调用父类的registerDefaultFilters方法初始化过滤规则。

org.springframework.context.annotation.ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    // 循环配置的多个路径
    for (String basePackage : basePackages) {
        // 找一个目录下的所有BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

        // 循环这些BeanDefinition
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }

            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);

                // 把BeanDefinition注册进registry(这个registry就是BeanFactory)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

下面来看看匹配逻辑,就是:为什么只有常用的那几个注解才会被解析成Bean?自定义的不行

扫描器父类:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

父类的  注册默认过滤规则  方法

@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
    // 上来就把Component注解加进去
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));

    // 后面两个分别是tomcat和标准java的两个注解
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

这里提一下includeFilters 和 excludeFilters

includeFilters是配合use-default-filters一起使用,只有use-default-filters=false才行,下面配置表示Spring只扫描用Controller注解修饰的类

<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

excludeFilters在use-default-filters=true或者不配置use-default-filters时可用,下面配置表示Spring不会管理用Controller注解修饰的类

<context:component-scan base-package="org.liuwei.smart.service">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

 说完这个,其实已经解释了为什么自定义的不行,因为还要配置才行,比如:自定义一个特殊的目录,目录下的class只有用自定义的注解才会被Spring管理

<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.liuwei.annotation.MyService"/>
</context:component-scan>

 

 继续看查询Bean的逻辑,依然是父类提供的方法,方法很简单:

1,遍历目录下所有的资源(class),

2,逐一判断是否符合Bean条件;

// 查询候选的组件bean
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 要扫描的路径
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 获取全部的Class
        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
                        else {
                            if (debugEnabled) {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

 校验是否符合Bean的条件,逻辑match方法中

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    // excludeFilters剔除哪些注解,通常不会配置
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return false;
        }
    }

    // includeFilters包含的注解,通常也不配置,但是有默认值
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

调用TypeFilter的match方法,上面已经分析了includeFilters里面放的TypeFilter是封装了Component.class。

    // 上来就把Component注解加进去
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));

 

org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter

过滤器父类提供的匹配模板方法 ,具体的匹配规则在子类中实现

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {

    // This method optimizes avoiding unnecessary creation of ClassReaders
    // as well as visiting over those readers.
    if (matchSelf(metadataReader)) {
        return true;
    }
    ClassMetadata metadata = metadataReader.getClassMetadata();
    if (matchClassName(metadata.getClassName())) {
        return true;
    }
    
    // 略去部分代码

    return false;
}

 

过滤器子类:org.springframework.core.type.filter.AnnotationTypeFilter 

@Override
protected boolean matchSelf(MetadataReader metadataReader) {
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    // 这里面两个判断逻辑
    // metadata.hasAnnotation 是否被@Component注释
    // metadata.hasMetaAnnotation 是否被@Component注释的子注解注释
    return metadata.hasAnnotation(this.annotationType.getName()) ||
            (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor

这是资源的注解元数据(可能的Bean对象的元数据),它有两个成员变量:

  • Set<String> annotationSet :类的头上加了哪些注解,你可能给一个类加n个注解,都会记录在这个集合里面,在加载类的时候就会初始化;
  • Map<String, Set<String>> metaAnnotationMap :key=注解,value=父注解  (测试结果如此,没有详细考究)

具体的判断逻辑则是先从Set集合中找,没有的话再到Map中找,找到了则返回true,表示是一个Bean。

例如:用Service标注

Set集合记录:[org.springframework.stereotype.Service]

Map集合记录:{org.springframework.stereotype.Service=[org.springframework.stereotype.Component]}

但是includeFilters集合中的数据是Component而不是Service,所以通过集合校验不成立,再通过Map校验,逻辑见下面代码片段,方法:hasMetaAnnotation,拿value中的数据与includeFilters比对,这样就匹配成功了。

至此,把Bean封装成BeanDefinitionHolder数组返回给处理器。

protected final Set<String> annotationSet = new LinkedHashSet<String>(4);

protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);

public boolean hasAnnotation(String annotationName) {
    // 集合操作
    return this.annotationSet.contains(annotationName);
}

@Override
public boolean hasMetaAnnotation(String metaAnnotationType) {
    Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
    for (Set<String> metaTypes : allMetaTypes) {
        if (metaTypes.contains(metaAnnotationType)) {
            return true;
        }
    }
    return false;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章