• 让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在微服务领域立下了汗马功劳!

  • 相关阅读:
    洛谷 P1414 又是毕业季II Label:None
    洛谷 P1372 又是毕业季I Label:None
    洛谷 P1111 修复公路 Label:并查集
    高精度特别策划 加减乘除余~~~
    洛谷 P1967 货车运输 Label: 倍增LCA && 最小瓶颈路
    数组指针和指针数组的区别
    堆和栈的区别
    JAVA8 十大新特性详解
    自己在菜单栏中加了一项打开文件的菜单后窗口不刷新 单击才刷新
    Windows 7 OpenGL配置
  • 原文地址:https://www.cnblogs.com/heartlake/p/12936633.html
Copyright © 2020-2023  润新知