首页 > 其他分享 >springboot 自动配置原理

springboot 自动配置原理

时间:2024-07-05 23:52:32浏览次数:13  
标签:springboot springframework 自动 原理 Import metadata packageNames class configuratio

@SpringBootApplication

发现是一个复合注解 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 由三个注解组合而来

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}
  1. @SpringBootConfiguration 等效于 @Configuration 注解(@Configuration 如果不清楚去看看前面 spring 的文章)

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }
    
  2. @ComponentScan 是用来配置哪些包能被扫描,注意这里只是规定哪些被扫描,并不会把这些包下面的类没有注册到容器(@EnableAutoConfiguration 会读取 @ComponentScan 配置的信息,然后根据配置进行注册)

    简言之就是 @ComponentScan 只管配置,@EnableAutoConfiguration 会根据配置来注册

  3. @EnableAutoConfiguration 这个是核心,完成自动配置的注解,专门来说说这个

@EnableAutoConfiguration

可以看出是由两个注解的复合注解 @AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

@AutoConfigurationPackage

  1. 这个注解的元注解可以看出,使用 @Import 导入了一个 AutoConfigurationPackages.Registrar 类型的 bean

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }
    
  2. @Import 前面说的很清楚了,有三种用法

    @Import(A.class)                               // 导入普通类
    @Import(MyImportSelector.class)                // 导入实现了 ImportSelector 的类(可以批量注册 bean,方法返回的数组就是要导入的 bean)
    @Import(MyImportBeanDefinitionRegister.class)  // 导入实现了 ImportBeanDefinitionRegistrar 的类(也可以批量,通过注册 BeanDefinition 的方式)
    

    @Import(AutoConfigurationPackages.Registrar.class) 就是使用的第三种方式,看看源码(这是个内部类)

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // metadata 表示注解元信息,获取使用了这个注解的那个类上的注解信息(我们是从 SpringBootApplication 跟进来的,也就是启动类)
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    
    }
    

    register 方法做了什么(spring 的 方方面面详细讲了 @Import 怎么注册 BeanDefinition 的,这里再大概看下)

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages#register
    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 如果容器中的 beanFactory.beanDefinitionMap 已经有这个 bean 了(初识起动不会有)
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            // BeanDefinition 对象
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // 注册 BeanDefinition
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }
    

    new PackageImports(metadata) 做了什么,是在读取 @ComponentScan 配置的信息

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages.PackageImports#PackageImports
    PackageImports(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
        List<String> packageNames = new ArrayList<>();
        // 先找 basePackages 属性
        for (String basePackage : attributes.getStringArray("basePackages")) {
            packageNames.add(basePackage);
        }
        // 再找 basePackageClasses 属性
        for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
            packageNames.add(basePackageClass.getPackage().getName());
        }
        // 如果都没配置,那就使用启动类所在的包
        if (packageNames.isEmpty()) {
            // metadata.getClassName() 启动类的包
            packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        this.packageNames = Collections.unmodifiableList(packageNames);
    }
    

    这里有一点要注意,如果什么都不配置,只会导入启动类这一个包,并不是往上说的子包及以下的包

    比如我定义一个包里面放一个 Controller(UserController)为什么这个也会被注册到容器,刚不是说子包不会被扫描到吗?

    因为这只是 spring 的容器,springMVC 初始化的时候,自己还有个容器,会把这些再进行注册!

@Import(AutoConfigurationImportSelector.class)

刚才的 @Import 是导入了一个实现了 ImportBeanDefinitionRegistrar 的类,这个 @Import 导入了一个实现了 ImportSelector 的类AutoConfigurationImportSelector 类继承和实现关系:AutoConfigurationImportSelector > DeferredImportSelector > ImportSelector

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 主要看这个方法返回的什么(返回的数组都要被导入容器)
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
          getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
          + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    // 调用了本类的 loadSpringFactories 方法,就是下面的方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            // 找所有 jar 的 META-INF/spring.factories 文件,里面配置的
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
           ....
        } catch (IOException var13) {
            IOException ex = var13;
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
        }
    }
}

找到的所有的类都要注册到容器吗,答案肯定不是的,不然太多了,这里就需要按需加载了

用 spring-boot-autoconfigure 这个 jar 为例,这个很典型,springboot 自己的,里面类容也是最多的,有的 jar 里面跟没没有这个文件

随便拎一个 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration 类,进去看看

标签:springboot,springframework,自动,原理,Import,metadata,packageNames,class,configuratio
From: https://www.cnblogs.com/cryus/p/18286785

相关文章

  • ChatGPT对话:Python程序自动模拟操作网页,无法弹出下拉列表框
    【编者按】需要编写Python程序自动模拟操作网页。编者有编程经验,但没有前端编程经验,完全不知道如何编写这种程序。通过与ChatGPT讨论,1天完成了任务。因为没有这类程序的编程经验,需要边学习,边编程,遇到问题再网上查资料。如果没有ChatGPT,估计至少需要5天时间。从登录开始模拟......
  • SpringBoot整合Dubbo的快速使用教程
        目录一、什么是Dubbo?二、SpringBoot整合Dubbo 1、父工程引入依赖2、各个Dubbo服务子模块引入依赖3、服务提供者 (1)启动类添加注解@EnableDubbo(2)服务类添加注解@DubboService(3)配置文件配置dubbo的信息4、服务的消费者 (1)启动类添加注解@EnableDubbo(2)接......
  • springboot的设计与实现(文档+源码)校园周边美食探索及分享平台
    大家好,我是永钊,一个混迹在java圈的码农,今天要和大家聊的是一款基于springboot的校园周边美食探索及分享平台,项目源码请联系永钊,目前有各类成品毕设javawebsshssmspringboot等等项目框架,源码丰富,欢迎咨询。 本网站系统研究了基于Spring Boot框架的校园周边美食探索及......
  • springboot校园资产管理的设计与实现(文档+源码)
    大家好,我是永钊,一个混迹在java圈的码农,今天要和大家聊的是一款基于springboot的校园资产管理,项目源码请联系永钊,目前有各类成品毕设javawebsshssmspringboot等等项目框架,源码丰富,欢迎咨询。 本网站系统利用当下成熟完善的SpringBoot框架,使用跨平台的可开发大型商业网......
  • web自动化(三)鼠标操作&键盘
    selenuim键盘操作importtimefromselenium.webdriver.common.keysimportKeysfromselenium.webdriver.common.byimportByfromselenium.webdriver.supportimportexpected_conditionsasECfromselenium.webdriver.support.waitimportWebDriverWaitfroms......
  • Springboot+Shiro+Mybatis+mysql实现权限安全认证
    Shiro是Apache的一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。Shiro主要分为两个部分就是认证和授权两部分一、介绍Subject代表了当前用户的安全操作SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组......
  • springboot+vue+mybatis实验室管理系统+PPT+论文+讲解+售后
    实验室管理系统提供给用户一个实验室信息管理的系统,最新的实验室信息让用户及时了解实验室管理动向,,还能通过交流区互动更方便。本系统采用了B/S体系的结构,使用了java技术以及MYSQL作为后台数据库进行开发。系统主要分为系统管理员、学生和教师三个部分,系统管理员主要功能包括......
  • 基于SpringBoot+Vue+uniapp的民族婚纱预定系统的详细设计和实现(源码+lw+部署文档+讲
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的车辆管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的库存管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......