zoukankan      html  css  js  c++  java
  • 让SpringBoot自动化配置不再神秘

    本文若有任何纰漏、错误,还请不吝指出!

    注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory。关于BeanFactory,后面有机会会再说下。

    花絮

    几年前接触过SpringBoot,跑过Demo,当时刚入行,连Spring都没搞明白,更别说SpringBoot了,就是觉得,哇塞,好厉害,然后一脸懵逼。

    工作中没有用到,又没有去主动学习它。觉得很恐惧,这么厉害的东西,肯定是很深奥,很复杂吧!。

    这种心理也造成了一定程度上,对某些事物的望而却步,其实只要向前迈出了步子,一步步慢慢来,才发现,以前的那种恐惧心理是多么的幼稚、胆怯、可笑!

    序言

    SpringBoot本身并没有多大的花样,所有的知识点其实还都是Spring Framework的。

    SpringBoot之前,使用Spring可以说,并不是那么的方便,其实也主要是在搭建一个基于Spring Framework的项目时这个困扰。Spring本身的配置,整合SpringMVC,整合Struts2,整合mybatis,整合Hibernate,整合SpringSecurity等等,如果是Web应用还有个web.xml需要配置。什么都要你去配置一下,第一步就是去找怎么配置,记住这么配置是如何配的,其实并没有切实的意义,毕竟又不是经常需要去搭建一个项目。正因为不常这么配置,不值得记住如何配置,导致每次实际用到时,很麻烦,到处去找如何配置的XML配置文件。

    SpringBoot的出现,正是为了解决这个问题,让你可以不去做任何配置的情况下,运行一个Spring应用,或者Web应用。需要做的仅仅是引入SpringBootmaven或者gradle依赖即可。

    SpringBoot要做的就是,让你开箱即用!

    将使用Spring的成本降到尽可能低,为用户带来了极大的便利。

    当然SpringBoot做的也不仅仅只有这些,不过这里仅讨论下它的自动化配置,不讨论其他的。

    如果了解Spring@Configuration这个注解的处理过程,会更加容易理解SpringBoot的自动化配置。

    如果没有,可以参考这篇解释

    穷其林

    这第一件事,就是找门,门都找不到,那不是没门吗!

    既然想找门,就得从程序的启动入口去找,任何SpringBoot程序都会用到这么两个

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

    看到这个后,如果好奇其实现,应该会首先查看SpringApplication#run方法,实际调用的是这个重载的静态方法。

    // org.springframework.boot.SpringApplication
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
       return new SpringApplication(primarySources).run(args);
    }
    
    public ConfigurableApplicationContext run(String... args) {
        ···省略···
        try {
            ···省略···
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
            // 真正启动应用程序上下文前的一些准备动作
            // 这里会去将Application.class,注册到org.springframework.context.annotation.AnnotatedBeanDefinitionReader
            // 也就是去把Application.class注册成一个BeanDefinition实例
            // 不过Application必须要是一个@Component
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新上下文,这个过程中主要就是Bean的实例化和属性赋值绑定
            // 如果是Web环境,涉及到Web相关的一些东西,但是本质上还是各种Bean的实例化
            // 和Bean之间依赖关系的处理,代理Bean的生成(涉及到AspectJ的Advice处理)等等
            refreshContext(context);
    
        }
        return context;
    }
    

    BeanDefinition实例有了,就能去启动上下文,处理Bean容器了,容器启动完成后,整个SpringBoot程序基本启动完成!

    等等! 是不是少了什么?

    这里就注册了一个BeanDefinition,那么多@Component@Configuration@Service@Controller怎么办?

    先留着疑问,且待后面解答!

    遇山口

    林尽水源,便得一山,山有小口,仿佛若有光。

    注意到上面的准备阶段,被注册的Bean必须要被@Component注解,现在Application.class仅有一个注解@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@Component所注解,这就解释了为什么被@SpringBootApplication所注解的Application.class类可以被作为一个Bean注册到BeanDefinitionRegistry

    除此之外,还有个令人惊喜的名称:@EnableAutoConfiguration,看名字就看出来它是做啥的了。

    没错,SpringBoot的所谓自动配置,就是它在起作用。

    这里暂时不讨论@ComponentScan

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

    这个注解又使用了两个注解,分别是@AutoConfigurationPackage@Import

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }
    

    可以发现,这两个注解最终都指向了同一个注解@Import

    @ImportAnnotation时代的<import/>,作用是向BeanDefinitionRegistry注册Bean的。

    所以@EnableAutoConfiguration这个注解一共注册了两个Bean,分别是:AutoConfigurationPackages.Registrar.classAutoConfigurationImportSelector.class

    先说说AutoConfigurationPackages.Registrar的用处

    这个类就干一个事,注册一个Bean,这个Bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,它有一个参数,这个参数是使用了@AutoConfigurationPackage这个注解的类所在的包路径。有了这个包路径后,就会扫描这个包下的所有class文件,然后将需要注册到Bean容器的类,给注册进去。

    具体可以参见这里 org.springframework.boot.autoconfigure.AutoConfigurationPackages#register

    这里就解释了为什么有时候主配置类放的位置不对,导致有些类没被Spring容器纳入管理

    桃花源

    经历了一番折腾,就要进入桃花源了

    AutoConfigurationImportSelector就是那最后一层窗户纸

    // org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
          ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            // 为了加载spring-boot-autoconfiguration包下的配置文件META-INF/spring-autoconfigure-metadata.properties
            // 这里配置的主要是一些SpringBoot启动时用到的一些@ConditionOnClass的配置
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            // 这里的AutoConfigurationEntry,就包含了所有的导入的需要被实例化的Bean
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                    annotationMetadata);
            // 返回这些被导入Bean的类全限定名数组
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    
        protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            ··· 省略 ···
            // 获取所有的需要导入的Bean,这些被导入的Bean就是各个组件需要自动化配置的启动点
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
            ··· 省略 ···
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    
        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            // 使用SpringFactoriesLoader#loadFactoryNames方法,从所有的包及classpath目录下,
            // 查找META-INF/spring.factories文件,且名称为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置
            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;
        }
    }
    

    最终这些配置在META-INF/spring.factories中需要自动配置的类,就会被注册到Spring Bean容器中,然后被实例化,调用初始化方法等!

    这些做自动配置的类,基本都会通过实现各种Aware接口,获取到Spring Framework中的BeanFactoryApplicationContext等等所有的一些框架内的组件,用于后面使用。

    之后完成自己框架的一些初始化工作,主要就是将原先和Spring整合时,需要手动配置的那些,在这里通过编程式的方式,给做了。

    这样,就完成了所谓的自动化配置,全程不需要我们的任何参与。

    PS: 这个仅仅是做了一个通用的配置,让用户可以在不做任何配置的情况下能直接使用。但是一些个性化的配置,还是需要通过配置文件的方式,写入配置。对于这部分配置的处理,SpringBoot也都给揽下了

    总结

    整体看下来,SpringBoot干的这些,更像是一个体力活,将于Spring集成的那么多三方库的配置,使用代码全部实现了一遍,其使用的核心功能,依然是Spring Framework的那些东西。

    但是这个体力活是为使用者省下的,也让Spring Framework更加的具有活力了。

    同时微服务的兴起,也是Spring为了顺势而必须作出的一个改变,也可以说为Spring在微服务领域立下了汗马功劳!

  • 相关阅读:
    几种常用的曲线
    0188. Best Time to Buy and Sell Stock IV (H)
    0074. Search a 2D Matrix (M)
    0189. Rotate Array (E)
    0148. Sort List (M)
    0859. Buddy Strings (E)
    0316. Remove Duplicate Letters (M)
    0452. Minimum Number of Arrows to Burst Balloons (M)
    0449. Serialize and Deserialize BST (M)
    0704. Binary Search (E)
  • 原文地址:https://www.cnblogs.com/heartlake/p/12936633.html
Copyright © 2011-2022 走看看