美文网首页
你不可不知道的 SpringBoot 启动配置原理!

你不可不知道的 SpringBoot 启动配置原理!

作者: 喊我小王吧 | 来源:发表于2020-03-16 22:57 被阅读0次

    SpringBoot 的启动原理

    @[toc]

    使用Spring Boot

    首先打开IDEA 创建一个Spring Boot项目

    选中 SpringInitializer 然后next 过程就不累赘了。

    然后打开pom文件我们发现Srping Boot 有个配置依赖

    启动依赖
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
    测试依赖
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            
    

    点击启动依赖后我们发现这里面各种依赖
    原来启动依赖里包含了spring-boot-starter 启动依赖,mvc依赖 还有tomcat 以及json 转换包 还有validator验证的包等等

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
            </dependency>
        </dependencies>
    

    然后在根目录下还有一个主程序,启动类

    @SpringBootApplication
    public class SpringDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringDemoApplication.class, args);
        }
    
    }
    
    

    如果主启动类没有被 @SpringBootApplication 标注,启动时会报一个错误:Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

    @SpringBootApplication 启动注解

    点进去后发现这是个组合注解

    
    @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 {
     ....
     }
    

    发现有@SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan这么三个注解

    可以说明 @SpringBootApplication注解是个组合注解
    我们也可以在启动类上使用@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 来启动Spring Boot

    先来看@ComponentScan 注解

    @ComponentScan(
        excludeFilters = {@Filter(
        type = FilterType.CUSTOM,
        classes = {TypeExcludeFilter.class}
    ), @Filter(
        type = FilterType.CUSTOM,
        classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    

    这个注解作用是扫描指定包下的路径,指定了两个过滤条件 TypeExcludeFilterAutoConfigurationExcludeFilter

    这两个应该在启动的时候是过滤一些包 或者在启动的时候给容器中加一些组件

    TypeExcludeFilter 类源码中 有个 match 方法 作用过滤的判断逻辑

        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            if (this.beanFactory instanceof ListableBeanFactory && this.getClass().equals(TypeExcludeFilter.class)) {
                Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory)this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
                Iterator var4 = delegates.iterator();
    
                while(var4.hasNext()) {
                    TypeExcludeFilter delegate = (TypeExcludeFilter)var4.next();
                    if (delegate.match(metadataReader, metadataReaderFactory)) {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
    

    if (this.beanFactory instanceof ListableBeanFactory 这句可以看到 BeanFactory 底层Ioc容器, 去执行自定义的过滤方法 TypeExcludeFilter 的作用是做扩展的组件过滤

    同理 AutoConfigurationExcludeFilter 里面也有一个 match 方法 可以看到源码中this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader) 判断了是不是配置和自动配置 ,暂且先不用关注。

    
    
    public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
        private ClassLoader beanClassLoader;
        private volatile List<String> autoConfigurations;
    
        public AutoConfigurationExcludeFilter() {
        }
    
        public void setBeanClassLoader(ClassLoader beanClassLoader) {
            this.beanClassLoader = beanClassLoader;
        }
    
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
        }
    
        private boolean isConfiguration(MetadataReader metadataReader) {
            return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
        }
    
        private boolean isAutoConfiguration(MetadataReader metadataReader) {
            return this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
        }
    
        protected List<String> getAutoConfigurations() {
            if (this.autoConfigurations == null) {
                this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
            }
    
            return this.autoConfigurations;
        }
    }
    

    然后看 @EnableAutoConfiguration 注解

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({EnableAutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    
    

    可以看到 有@AutoConfigurationPackage
    @Import({EnableAutoConfigurationImportSelector.class})

    这两个注解

    首先先看 @AutoConfigurationPackage 注解从名字上来看是自动配置包 ,点进去后
    @Import({Registrar.class}) public @interface AutoConfigurationPackage { } 发现 给@ 注解导入一个组件 @Import({Registrar.class})

    Registrar 组件

       @Order(-2147483648)
       static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
           Registrar() {
           }
    
           public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
               AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
           }
    
           public Set<Object> determineImports(AnnotationMetadata metadata) {
               return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
           }
       }
    

    debug一下可以看到 这个注解作用是导入我们根目录经下的包名,通过包名导入组件,也就是扫描各种bean 然后注册到Ioc容器中。


    在这里插入图片描述

    然后来研究一下 @Import({EnableAutoConfigurationImportSelector.class})
    貌似这个也是导入组件
    打开 EnableAutoConfigurationImportSelector

    public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector
    

    再打开 AutoConfigurationImportSelector 会看到如下代码

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
     
    

    此类实现了 DeferredImportSelector接口 你会发现这个类继承了ImportSelector 这个类

    public interface DeferredImportSelector extends ImportSelector 
    

    作用是DeferredImportSelector 的执行时机比 ImportSelector 更晚,导入组件后会去查找导入组件结果。

    然后我们还发现有下面这段代码 selectImports 这个方法按名字来看是查找导入的组件 ,这也证明了AutoConfigurationImportSelector继承DeferredImportSelector这个接口的意义。

        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                try {
                    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                    AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                    configurations = this.removeDuplicates(configurations);
                    configurations = this.sort(configurations, autoConfigurationMetadata);
                    Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                    this.checkExcludedClasses(configurations, exclusions);
                    configurations.removeAll(exclusions);
                    configurations = this.filter(configurations, autoConfigurationMetadata);
                    this.fireAutoConfigurationImportEvents(configurations, exclusions);
                    return (String[])configurations.toArray(new String[configurations.size()]);
                } catch (IOException var6) {
                    throw new IllegalStateException(var6);
                }
            }
        }
    
    

    可以看到下面这段代码

    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    

    好像是配置了一个配置集合

    点进去后发现以下代码

    SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    

    this.getSpringFactoriesLoaderFactoryClass()

     protected Class<?> getSpringFactoriesLoaderFactoryClass() {
            return EnableAutoConfiguration.class;
        }
    

    传入的Class就是 @EnableAutoConfiguration ,而这个类中
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; 开启自动配置

    SpringFactoriesLoader.loadFactoryNames 扫描了类路径下


    在这里插入图片描述

    使用 classLoader 去加载了指定常量路径META-INF/spring.factories下的资源

    在这里插入图片描述

    我们会发现 多个包下有 spring.factories文件被加载

    然后debug继续往下走发现

    AutoConfigurationImportSelector类中 selectImports方法里 configurations = this.filter(configurations, autoConfigurationMetadata);这段代码进行过滤

    传入的 参数 autoConfigurationMetadata 是上面代码AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); 查询结果

    点进去发现加载了 所有的spring-autoconfigure-metadata.properties文件
    然后发现这个文件里 基本都是以org.springframework.boot.autoconfigure开头的全路径包名 ,瞬间明白了

    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    这个类是找到所有Spring Boot支持的自动配置的类

    在这里插入图片描述

    然后过滤加载的自动配置包里是否含有在类路径下导入

    AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next(); 自动配置导入过滤器

    在这里插入图片描述

    排除类路径下不存在的自动配置 最后是20个默认的自动配置 加入到Ioc容器中


    在这里插入图片描述

    总结

    基本上Spring Boot的自动配置原理大致上分为三步:

    • 1 过滤器首先在启动类上过滤或者给容器添加组件
    • 2 扫描根路径下所有的bean
    • 3 添加类路径META-INF/spring.factoriesMETA-INF/spring-autoconfigure-metadata.properties自动配置的包和过滤自动配置是否在类路径下有包最后注册到Ioc中

    关注我

    长按二维码

    image

    如果你喜欢这篇文章,喜欢,在看,转发。

    相关文章

      网友评论

          本文标题:你不可不知道的 SpringBoot 启动配置原理!

          本文链接:https://www.haomeiwen.com/subject/ckraehtx.html