8.ClassPathBeanDefinitionScanner的作用


highlight: arduino-light

八.ClassPathBeanDefinitionScanner

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

功能1:扫描BeanDefinition

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

功能2:注册BeanDefinition

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

image.png

初始化

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

```java    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);   } ```

构造函数

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

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

设置默认的扫描过滤器Filter

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

```java 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的方法。

```java protected void registerDefaultFilters() {        /*         *注册了@Component过滤器到includeFiters         *相当于所有加了@Component的注解即继承@Component或者说是派生于@Component的注解         *包括@Component @Configuration @Controller @Service @Repository 等注解         *只要加了这些注解的bean,Spring都能扫描到         */        this.includeFilters.add(new AnnotationTypeFilter(Component.class));

//类加载器,这个没啥好解释的
        ClassLoader cl =        
                ClassPathScanningCandidateComponentProvider.class.getClassLoader();

        /****
         * 同时也支持javaEE6的javax.annotation.ManagedBean和JSR-330的@Named注解
         */         
        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里面添加需要扫描的特定注解。

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

1.添加@Component对应的TypeFilter

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

2.添加@ManagedBean、@Named的TypeFilter

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

为什么呢?

因为这2个注解都是用反射去加载到类过滤器,如果没有引用相应的依赖,必然加载不到对应的类,那么就会抛出异常进入try catch。

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

scan

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

java @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才有发挥的作用,这个类就是为程序员手动扫描用的。

言外之意,如果你不手动调用context.scan("com");这个初始化的scanner就没有用武之地。

而spring内部的自动扫描不会使用初始化的scanner,spring会重新生成新的ClassPathBeanDefinitionScanner完成扫描。

从代码角度来讲完成的功能一模一样。

java /** * 扫描给定的包路径,生成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方法:

```java   /*     * 扫描给定的包路径,生成BeanDefinition并注册到注册器     */    protected Set doScan(String... basePackages) {        Assert.notEmpty(basePackages, "At least one base package must be specified");        Set beanDefinitions = new LinkedHashSet<>();        / *         * 扫描basePackage路径下的java文件         * 先全部转为Resource,然后再判断拿出符合条件的bd         */        for (String basePackage : basePackages) {            //调用findCandidateComponents扫描包组装BeanDefinition集合            //findCandidateComponents方法是从父类继承的方法            Set 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。

java  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*:com*.class
2.扫描路径:classpath*:com*.class
3.解析类的注解信息
4.判断excludeFilters中是否包含类上的注解,包含返回false

false代表非候选,true代表候选

5.判断includeFilters中是否包含类上的注解,包含继续判断条件满足

false代表非候选,true代表候选,如果excludeFilters和includeFilters都包含,返回false代表非候选。

6.创建bd并将bd加入set

```java /* * 扫描包,生成BeanDefinition集合 */ private Set scanCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet<>(); try { // //根据包名组装包扫描路径 // 如classpath :com/*/.class String packageSearchPath =
ResourcePatternResolver.CLASSPATHALLURL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; /* resourcePatternResolver即资源加载器,资源加载器其实就是容器本身 资源加载器根据匹配规则获取Resource[] 可以理解为资源加载器根据包扫描路径扫描指定路径下的所有class文件。 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)) {
                    //把符合条件的类转换成ScannedGenericBeanDefinition
                    //现在知道ClassPathBeanDefinitionScanner扫描出来的bd
                    //为什么是ScannedGenericBeanDefinition了吧
                    //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");
                        }
                        //添加到候选集合
                        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

是否满足候选通过includeFilters与excludeFilters来判定匹配。 1.判断excludeFilters中是否包含类上的注解,包含直接返回false,否则继续往下走。 2.判断includeFilters中是否包含类上的注解,包含返回isConditionMatch(metadataReader);,否则继续往下走。 3.直接返回false。

false代表非候选,true代表候选,如果excludeFilters和includeFilters都包含返回false代表非候选。

也就是excludeFilters的优先级大于includeFilters。

java protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { //看下是否在排除的filter内 如果是返回false for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } //看下是否在包含的filter内 如果是继续判断条件是否匹配 for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { //判断条件是否匹配 return isConditionMatch(metadataReader); } } return false; }

isCandidateComponent

java /** * 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(basePackage)下面的for循环 for (BeanDefinition candidate : candidates),开始循环,处理注解,设置beanDefinition属性 最后执行 registerBeanDefinition(definitionHolder, this.registry);注册beanDefinition

registerBeanDefinition(definitionHolder, this.registry);

跟进此方法:

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);

```java  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);
            }
        }
    }

```

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

image.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值