逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器

       spring包扫描是通过ClassPathBeanDefinitionScanner类来完成的,它主要工作有两个:

  1. 扫描类路径下的候选Component,构造BeanDefinition对象(实际为ScannedGenericBeanDefinition)
  2. 利用BeanDefinitionRegister注册BeanDefinition到bean工厂中,BeanDefinitionRegister是spring默认bean工厂DefaultListableBeanFactory的一个接口,用于注册BeanDefinition;
    先祭出类继承图:


    ClassPathBeanDefinitionScanner类继承图

初始化

       ClassPathBeanDefinitionScanner在spring启动的时候完成初始化的:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
    }
}
——————————————————————————————————————————————————————————————————

    public AnnotationConfigApplicationContext() {
        //在IOC容器中初始化一个 注解bean读取器AnnotatedBeanDefinitionReader
        this.reader = new AnnotatedBeanDefinitionReader(this);
        //在IOC容器中初始化一个 按类路径扫描注解bean的 扫描器
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

构造函数

//spring将bean工厂传递进去
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        this(registry, true);
    }

//useDefaultFilters表示是否启动过滤器,用户提供过自定义滤器让spring忽略或者添加自己定义的业务类。
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
    }

       ClassPathBeanDefinitionScanner的构造函数有多个,真正完成构造函数初始化的是:

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment, @Nullable ResourceLoader resourceLoader) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//bean工厂,IOC容易,注册BeanDefinition
        this.registry = registry;

        if (useDefaultFilters) {
            /**
             * 注册spring扫描类过滤器
             * 加了特定注解的类会被扫描到
             * 带有@Component、@Repository、@Service、@Controller、@ManagedBean、@Named
             */
            registerDefaultFilters();
        }
        setEnvironment(environment);
        setResourceLoader(resourceLoader);
    }

       我们主要关注registerDefaultFilters方法,这个方法是注册过滤器,过滤器用来过滤 从指定包下面查找到的 Class ,如果能通过过滤器,那么这个class 就会被转换成BeanDefinition 注册到容器。如果在实例化ClassPathBeanDefinitionScanner时,没有说明要使用用户自定义的过滤器的话,那么就会采用默认的过滤器规则。registerDefaultFilters()是父类ClassPathScanningCandidateComponentProvider的方法:

    protected void registerDefaultFilters() {
        /*
         *注册了@Component 过滤器到 includeFiters ,相当于 同时注册了所有被@Component注释的注解,
         *包括@Service ,@Repository,@Controller,同时也支持java EE6 的javax.annotation.ManagedBean
         *和JSR-330的@Named 注解。
         *这就是为什么@Service @Controller @Repostory @Component 能够起作用的原因。
         */
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        //类加载器,这个没啥好解释的
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            // 添加ManagedBean 注解过滤器
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
            logger.trace("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 {
            // 添加Named 注解过滤器
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
            logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

       首先这里的includeFilters大家熟悉吗,还有个excludeFilters,先看一下属性

private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();

       这里提前往includeFilters里面添加需要扫描的特定注解

  1. 添加元注解@Component,需要注意的是@Repository、@Service、@Controller里面都标注了@Component。很好理解,扫描的时候用includeFilters 去过滤时,会找到并处理这4个注解的类。
  2. 上面源码中的两个注解@ManagedBean、@Named需要有对应的jar包,否则(也就是说把这个方法走完),includeFilters里面只会有一个元素。其实按照spring的加载流程,ClassPathBeanDefinitionScanner到这里的作用就结束,里面的很多重要方法是在流程加载后面用到的,但是既然都是一个类里面的方法,就在这里先讲一下吧。

scan扫描

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.scan("com");
        context.refresh();
    }
}

       scan方法是开始扫描的入口,context.scan("com")->this.scanner.scan(basePackages);这个scanner就是默认构造函数初始化的ClassPathBeanDefinitionScanner,只有手动调用context.scan("com");这个初始化的scanner才有发挥的作用,这个类起始就是为程序员手动扫描用的,spring内部自动扫描会再生成一个ClassPathBeanDefinitionScanner对象完成扫描,从代码角度来讲完成的功能一模一样。

    /**
     * 扫描给定的包路径,生成BeanDefinition并注册到注册器
     */
    public int scan(String... basePackages) {
        ////获取注册器中已注册的bean数量
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        //通过doScan给定包路径并生成BeanDefinition注册到registry中
        doScan(basePackages);

        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }
        //返回本次扫描并注册到IOC容器中的类
        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }

       看源码可知,真正完成扫描的是doScan方法:

    /**
     * 扫描给定的包路径,生成BeanDefinition并注册到注册器
     */
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        /**
         * 扫描basePackage路径下的java文件
         * 先全部转为Resource,然后再判断拿出符合条件的bd
         */
        for (String basePackage : basePackages) {
            // 调用findCandidateComponents扫描包组装BeanDefinition集合
            //findCandidateComponents方法为父类方法
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            //遍历BeanDefinition,根据条件将BeanDefinition注册到注册中心
            for (BeanDefinition candidate : candidates) {
                //解析scope属性
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                 //获取beanName,先判断注解上有没有显示设置beanName
                 //没有的话,就以类名小写为beanName
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    /**
                     * 如果这个类是AbstractBeanDefinition类型
                     * 则为他设置默认值,比如lazy/init/destroy
                     * 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
                     */
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    /**
                     * 处理加了注解的类
                     * 把常用注解设置到AnnotationBeanDefinition中
                     */
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

先关注findCandidateComponents(basePackage);方法,进入到父类ClassPathScanningCandidateComponentProvider。

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
            return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        }
        else {
            //完成真正的扫描
            return scanCandidateComponents(basePackage);
        }
    }

根据方法名顾名思义,找到候选组件,在指定的包中找到候选组件,进入到scanCandidateComponents(basePackage);

/**
     *  扫描包,生成BeanDefinition集合
     */
    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            // //根据包名组装包扫描路径
            // 如,classpath*:com/**/*.class
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            /*
             resourcePatternResolver(资源加载器)根据匹配规则获取Resource[]
             Resource数组中每一个对象都是对应一个Class文件,Spring用Resource定位资源,封装了资源的IO操作。
             这里的Resource实际类型是FileSystemResource。资源加载器其实就是容器本身
            */
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            boolean traceEnabled = logger.isTraceEnabled();
            boolean debugEnabled = logger.isDebugEnabled();
            //循环处理每一个resource,相当于循环处理每一个class文件
            for (Resource resource : resources) {
                if (traceEnabled) {
                    logger.trace("Scanning " + resource);
                }
                if (resource.isReadable()) {
                    try {
                        /*
                         *读取类的注解信息和类信息,信息储存到MetadataReader
                         *meteDataFactory根据Resouce获取到MetadataReader对象
                         *MetadataReader提供了获取一个Class文件的ClassMetadata和AnnotationMetadata的操作。
                         */
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        /*
                         *判断元数据是否需要组装成BeanDefinition
                         *此处判断当前class是否需要注册到spring的IOC容器中,通过IOC容器管理。
                         *spring默认对Component注解的类进行动态注册到IOC容器
                         *通过includeFilters与excludeFilters来判定匹配。
                         */
                        if (isCandidateComponent(metadataReader)) {
                            // //把符合条件的 类转换成 BeanDefinition
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setSource(resource);
                            // 再次判断 如果是实体类 返回true,如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                //返回BeanDefinition 注册到 BeanFactory
                                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;
    }

       上面有一个组件判断if (isCandidateComponent(metadataReader)),就是判断当前class文件符不符合扫描过滤器includeFiltersexcludeFilters中的定义,最后返回一个符合条件的Set<BeanDefinition>。
       再回到之前的 doScan(String... basePackages)findCandidateComponents下面的方法for (BeanDefinition candidate : candidates),开始循环,处理注解,设置beanDefinition属性最后执行 registerBeanDefinition(definitionHolder, this.registry);注册beanDefinition

registerBeanDefinition(definitionHolder, this.registry);

跟进此方法:

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        /**
         * 这里的registerBeanDefinition是由父类GenericApplicationContext实现的
         *
         * 跟踪源码可知,是在父类中调用this.beanFactory.registerBeanDefinition(beanName, beanDefinition)
         * 而这个beanFactory是AnnotationConfigApplicationContext在执行自己的构造方法this()时
         * 先去执行了父类GenericApplicationContext的构造方法,完成了this.beanFactory = new DefaultListableBeanFactory()
         *
         * 所以,最终将beanDefinition注册到了DefaultListableBeanFactory中
         *
         * */
        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

我们对以上流程做个梳理:


扫描过程
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343