SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar 源码分析 (十)

简介: SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar 源码分析 (十)


概述

本篇来介绍一个Spring强大的扩展接口:ImportBeanDefinitionRegistrar,该接口主要用来注册beanDefinition。很多三方框架集成Spring 的时候,都会通过该接口,实现扫描指定的类,然后注册到spring 容器中。 比如 Mybatis 中的Mapper接口,springCloud中的 FeignClient 接口,都是通过该接口实现的自定义注册逻辑。

Mybatis中的扫描实现类如下:

源码分析

大致分两个步骤来介绍:

  • 注册所有的ImportBeanDefinitionRegistrar实现类
  • 执行所有的ImportBeanDefinitionRegistrar的逻辑
  • 自定义扫描注册BeanDefinition组件

分别来看。

注册所有的 ImportBeanDefinitionRegistrar 实现类

在 spring 容器启动加载配置类阶段,会执行配置类注解@Import的逻辑: 对应逻辑在ConfigurationClassParser中:

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
            throws IOException {
        if (visited.add(sourceClass)) {
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();
                if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                    collectImports(annotation, imports, visited);
                }
            }
            //收集所有的导入配置类
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }
复制代码

上述方法利用了递归解析,直至获取所有的导入类。

来看解析导入类逻辑:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                //类型判断是否为 ImportSelector类型
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    //这里进行类型判断是否为 ImportBeanDefinitionRegistrar 类型
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        //直接实例化
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        //递归解析
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }
复制代码

上述逻辑对@Import导入类进行判断,如果为ImportBeanDefinitionRegistrar类型,则直接实例化,并加入到ConfigurationClass集合中:

private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new LinkedHashMap<>();
复制代码

该集合在配置类解析完之后,会单独处理。

执行所有的 ImportBeanDefinitionRegistrar 的逻辑

ConfigurationClassBeanDefinitionReader通过loadBeanDefinitions方法来获取所有的BeanDefinition,最终会执行以下方法:

上述方法解析并读取配置类中的BeanDefinition,有一行比较关键:

loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
复制代码

可以看到,该方法传入的参数正是前面存入ConfigurationClass中的集合对象,也就是ImportBeanDefinitionRegistrar的所有实现类。 继续来看:

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
        registrars.forEach((registrar, metadata) ->
                registrar.registerBeanDefinitions(metadata, this.registry));
    }
复制代码

遍历所有的实现类,执行注册逻辑。 Mybatis,Feignclient大致做法:通过实现了该接口,然后注册各自指定的类或接口:mapper接口或者feignClient接口,然后将接口声明为 FactoryBean,设置拦截方法,生成代理类。

自定义扫描注册BeanDefinition组件

自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyAutoBeanDefinitionRegistrar3.class)
public @interface EnableMyAutoRegistrar3 {
}
复制代码

自定义注册实现类:

public class MyAutoBeanDefinitionRegistrar3 implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
    private ClassLoader classLoader;
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scan = getScanner();
        //指定注解,类似于Feign注解
        scan.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
        Set<BeanDefinition> candidateComponents = scan.findCandidateComponents("com.beanDefinition.registrar.component");
        BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
        candidateComponents.stream().forEach(beanDefinition -> {
            String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
            if (!registry.containsBeanDefinition(beanDefinition.getBeanClassName())) {
                registry.registerBeanDefinition(beanName, beanDefinition);
            }
        });
    }
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            // FeignClient 重写了 ClassPathScanningCandidateComponentProvider 匹配逻辑
            @Override
            protected boolean isCandidateComponent(
                    AnnotatedBeanDefinition beanDefinition) {
                if (beanDefinition.getMetadata().isIndependent()) {
                    // TODO until SPR-11711 will be resolved
                    // 判断接口是否继承了 Annotation注解
                    if (beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata()
                            .getInterfaceNames().length == 1 && Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
                        try {
                            Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(),
                                    MyAutoBeanDefinitionRegistrar3.this.classLoader);
                            return !target.isAnnotation();
                        } catch (Exception ex) {
                            this.logger.error(
                                    "Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
                        }
                    }
                    return true;
                }
                return false;
            }
        };
    }
}
复制代码

具体代码 Github: github.com/admin801122…

总结

本篇主要讲述了 Spring BeanDefinition 注册接口ImportBeanDefinitionRegistrar的用法,也是一些第三方框架整合 Spring 时的常用扩展接口。

看完本文,你应该能很好的理解@EnableFeignClients的注册原理了。



目录
相关文章
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
4675 14
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
1597 12
|
前端开发 Java 数据库
玩转springboot之springboot注册servlet
在Spring Boot中注册Servlet非常灵活,可以通过 `@WebServlet`注解快速注册,也可以通过 `ServletRegistrationBean`进行细粒度控制。通过这两种方式,可以满足各种场景下的需求,确保应用能够高效处理HTTP请求。
1234 14
|
JSON 缓存 前端开发
SpringBoot的 ResponseEntity类讲解(具体讲解返回给前端的一些事情)
本文讲解了SpringBoot中的`ResponseEntity`类,展示了如何使用它来自定义HTTP响应,包括状态码、响应头和响应体,以及如何将图片从MinIO读取并返回给前端。
1224 3
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
200 1
|
Java Spring 容器
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
767 0
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
|
Java Spring
idea新建spring boot 项目右键无package及java类的选项
idea新建spring boot 项目右键无package及java类的选项
781 5
|
Java 数据库连接 mybatis
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
|
Java 应用服务中间件 Maven
Springboot入门基础知识详解 parent starter 引导类 辅助功能
Springboot入门基础知识详解 parent starter 引导类 辅助功能
238 2
|
Java
springboot Test 测试类中如何排除一个bean类
springboot Test 测试类中如何排除一个bean类
633 0