likes
comments
collection
share

8.ClassPathBeanDefinitionScanner的作用

作者站长头像
站长
· 阅读数 17

八.ClassPathBeanDefinitionScanner

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

功能1:扫描BeanDefinition

扫描类路径下的候选Component,构造BeanDefinition对象即ScannedGenericBeanDefinition

功能2:注册BeanDefinition

利用BeanDefinitionRegister注册BeanDefinition到bean工厂中,BeanDefinitionRegister是spring默认bean工厂DefaultListableBeanFactory的一个接口,用于注册BeanDefinition。

8.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));
    }

设置默认的扫描过滤器Filter

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 @Configuration
             *同时也支持java EE6 的javax.annotation.ManagedBean
             *和JSR-330的@Named 注解。
             *这就是为什么
             *@Component @Service @Controller @Repostory   @Configuration 能够起作用
             */
            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));
              
            }catch (ClassNotFoundException ex) {   
            }
        
            try {
                // 添加Named 注解过滤器
                this.includeFilters.add(
                    new AnnotationTypeFilter(((Class<? extends Annotation>)         
                         ClassUtils.forName("javax.inject.Named", cl)), false));
            }catch (ClassNotFoundException ex) {
            }
        }

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

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

//包含的Filters 如果类上的注解在includeFilters存在 那么就需要被扫描
private final List<TypeFilter> includeFilters = new LinkedList<>();
//排除的Filters 如果类上的注解在Filters存在 那么就不需要被扫描
private final List<TypeFilter> excludeFilters = new LinkedList<>();

1.添加@Component对应的TypeFilter

添加元注解@Component,需要注意的是@Repository、@Service、@Controller里面都标注了@Component。很好理解,扫描的时候用includeFilters 去过滤时,会找到并处理这4个注解的类。

2.添加@ManagedBean、@Named的TypeFilter

上面源码中的两个注解@ManagedBean、@Named需要有对应的jar包,否则includeFilters里面只会有一个元素@Component对应的TypeFilter。

其实按照spring的加载流程,ClassPathBeanDefinitionScanner到这里的作用就结束,里面的很多重要方法是在流程加载后面用到的,但是既然都是一个类里面的方法,就在这里先讲一下吧。

scan

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new        
                            AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.scan("tyrant");
        context.refresh();
    }
}
@Override
public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    this.scanner.scan(basePackages);
}

scan方法是开始扫描的入口,context.scan("com")->this.scanner.scan(basePackages);

这个scanner就是AnnotationConfigApplicationContext的默认构造函数初始化的的时候创建的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) {
        //  这个方法很重要 注册了内置的ConfigurationClassPostProcessor
        AnnotationConfigUtils.
                    registerAnnotationConfigProcessors(this.registry);
    }
    //返回本次扫描并注册到IOC容器中的类
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

doScan

看源码可知,真正完成扫描的是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

先关注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);

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

1.拼接路径:classpath*:tyrant*.class
2.扫描路径:classpath*:tyrant*.class
3.解析类的注解信息
4.includeFilters是否包含类上的注解
5.includeFilters包含类上的注解
6.excludeFilters不包含类上的注解
7.创建bd将bd加入set
        /**
         *  扫描包,生成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 (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
                                //1.从metadataReader中取出metadata设置到bd中
                                //2.从metadata中取出ClassName设置到bd中
                                //3.从metadataReader中取出resource设置到bd中
                                ScannedGenericBeanDefinition sbd = new 
                                        ScannedGenericBeanDefinition(metadataReader);
                                sbd.setSource(resource);
                                // 再次判断 如果是实体类 返回true
                                //如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true
                                if (isCandidateComponent(sbd)) {
                                    if (debugEnabled) {
                                        logger.debug
                                            ("Identified candidate component class");
                                    }
                                    //返回BeanDefinition 注册到 BeanFactory
                                    candidates.add(sbd);
                                }
                                else {
                                    if (debugEnabled) {
                                    logger.debug
                                         ("Ignored,not a concrete top-level-class");
                                    }
                                }
                            }
                            else {
                                if (traceEnabled) {
                                logger.trace
                                        ("Ignored because not matching anyfilter");
                                }
                            }
                        }
                        catch (Throwable ex) {
                            throw new BeanDefinitionStoreException
                                ("Failed to read candidate component class: ");
                        }
                    }
                    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;
        }
isCandidateComponent:类是否符合

上面有一个组件判断if (isCandidateComponent(metadataReader)),就是判断当前class文件符不符合扫描过滤器includeFilters与excludeFilters中的定义,最后返回一个符合条件的Set。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    //看下是否在排除的filter内 如果是返回false
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false;
        }
    }
    //看下是否在包含的filter内 如果是返回true
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}
isCandidateComponent
    /**
        * metadata.isIndependent():不是内部类或者是静态内部类
        * metadata.isConcrete():不能是接口或抽象类
        * metadata.isAbstract():是抽象类、
        * metadata.hasAnnotatedMethods(Lookup.class.getName()):有lookUp注解标注
        * 总结就是需要满足以下两个条件:
        * 1. 不能是内部类或者是静态内部类
        * 2. 不能是接口或者抽象类,如果是抽象类,则需要LookUp注解标注
        */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            AnnotationMetadata metadata = beanDefinition.getMetadata();
            //1.普通的实体类
            //2.带@Lookup注解的方法的抽象类
            //上面2种情况返回true
            return (
            metadata.isIndependent() && (metadata.isConcrete()
            ||
            (metadata.isAbstract() &&       
                          metadata.hasAnnotatedMethods(Lookup.class.getName()))));
        }
registerBeanDefinition

再回到之前的 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) {
                    //this.aliasMap.put(alias, name);
                    //实际是别名作为key
                    //获取到实际名称后在获取对应的对象
                    registry.registerAlias(beanName, alias);
                }
            }
        }

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

8.ClassPathBeanDefinitionScanner的作用

转载自:https://juejin.cn/post/7236028062872846396
评论
请登录