写点什么

【原创】Spring Boot 终极篇《上》

用户头像
田维常
关注
发布于: 2020 年 11 月 04 日

关注公众号Java 后端技术全栈”**


回复“面试”获取全套面试资料


目前 Spring Boot 有多火,相信大家都能感受到,SpringBoot 仿佛现在成为一个 java 开发中必备的技能之一。另外一方面,其实真正只有使用过的人才知道 Spring Boot 的爽快,那是一种享受。但是想做一个合格的、有优秀的 java 开发者,Spring Boot 其背后的相关原理也是不得不掌握的。所以这一篇中我们来说 Spring Boot 的配置。


依赖配置的处理


在使用 SpringMVC 的时候,咱们会涉及到大量的配置、大量的依赖。但是 Spring Boot 的依赖是怎么样的呢?麻烦吗?


人总是懒惰的,各种各样的工具出现都是为人们的懒惰而出现的。


有了洗衣机就不用手洗衣服了。

有了车就不用走路了。

有了电话就不用写信了。

…..


看看 Spring Boot 的 pom 文件,我们可以看到 pom 文件中的 parent,点击 spring-boot-starter-parent:


<parent>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-parent</artifactId>      <version>2.1.6.RELEASE</version>      <relativePath /></parent>
复制代码


点击 spring-boot-starter-parent 进去之后发现。



熟悉的配置文件 yml、yaml、properties。另外看到有这一段 parent


<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-dependencies</artifactId>    <version>2.1.6.RELEASE</version>    <relativePath>../../spring-boot-dependencies</relativePath> </parent>
复制代码


spring-boot-dependencies 顾名思义,Spring Boot 的相关引用。点击 spring-boot-dependencies 进去



发现 parent 有<properties>值



这里就是为我们配置好了常用 jar 的版本。所以我们不用显性的配置版本号。(这里太多了只截图了部分,有兴趣按照我这步骤自行查看)。


如何实现自动配置


回到我们代码中的启动类里。


@SpringBootApplicationpublic class BlogApplication {    public static void main(String[] args) {        SpringApplication.run(BlogApplication.class, args);    }}
复制代码


这里面有个神奇的 main 函数,还有一个注解 @SpringBootApplication ,大家都知道注解的作用就是用来标记的,表示在某一时刻做某件事。我们点进去看下。


@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented//可以记录在javac中@Inherited//可以被子类击沉改注解@SpringBootConfiguration//标明改类为配置类@EnableAutoConfiguration//启动自动装配功能//扫描(前面文中中有说过)@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {    /**     * Exclude specific auto-configuration classes such that they will never be applied.     * @return the classes to exclude     */    @AliasFor(annotation = EnableAutoConfiguration.class)    Class<?>[] exclude() default {};
复制代码


可以看出注解 @SpringBootApplication 其实使用三个核心注解组合而成的。


@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan


@SpringBootConfiguration


第一个注解@SpringBootConfiguration就是一个标记类为配置类.可以被组件扫描器扫描到,和 @Configuration 注解功能相同。


@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {}
复制代码



@EnableAutoConfiguration


第二个注解@EnableAutoConfiguration,该 注解是启动自动配置功能。


这个注解是 Spring Boot 框架最重要的注解,也是实现自动化配置的注解。我们看下源码:


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


可以看到,它还是一个组合注解,@AutoConfigurationPackage 和 @Import。


@AutoConfigurationPackage


注解@AutoConfigurationPackage。这个注解的作用就是自动配置包。


看下源码:


@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited//spring 框架的底层注解,它的作用就是给容器导入某个组件类//这里就是将Registrar组件类导入到容器中@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {}
复制代码


Registrar


再看看Registrar类的源码,该类是一个静态内部类,


org.springframework.boot.autoconfigure.AutoConfigurationPackages中的静态内部类


 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {        //获取到的是项目主程序启动类所在的目录        //        @Override        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {            //默认将会扫描@SpringBootApplication注解标注的主配置类所在的包            //以及其子包下所有组件            register(registry, new PackageImport(metadata).getPackageName());        }        @Override        public Set<Object> determineImports(AnnotationMetadata metadata) {            return Collections.singleton(new PackageImport(metadata));        }    }
复制代码


这个类 registerBeanDefinitions 方法会将主程序类所在你的包以及子包下的组件都扫描到 spring 容器中。所以我们在项目进行包目录制定的时候,需要有个规范,这样才能被扫描到。


@Import


这个注解,其实我们上面看到了,就是将资源引入到容器中。这里是


@Import(AutoConfigurationImportSelector.class)


也就是说将 AutoConfigurationImportSelector 加载到组件中,我们看下这个类中的这个方法,这个方法就是将 Spring Boot 需要的组件都导入进来。


 @Override    public String[] selectImports(AnnotationMetadata annotationMetadata) {        //判断enableautoconfiguration注解是否已经开启。        //默认开启        if (!isEnabled(annotationMetadata)) {            return NO_IMPORTS;        }        //加载配置文件META_INF/spring-autoconfigure-metadata.properties        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader                .loadMetadata(this.beanClassLoader);        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,                annotationMetadata);        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());    }
复制代码


主要就是加载组件,哪会加载什么组件呢?就在这个 loadMetadata 这个方法中。这个方法中传递了当前的类的构造器,看看这个方法。


final class AutoConfigurationMetadataLoader {    protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";    private AutoConfigurationMetadataLoader() {    }    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {        return loadMetadata(classLoader, PATH);    }    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {        try {            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)                    : ClassLoader.getSystemResources(path);            Properties properties = new Properties();            while (urls.hasMoreElements()) {                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));            }            return loadMetadata(properties);        }        catch (IOException ex) {            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);        }    }
复制代码


可以发现,这个方法就是加载


spring-autoconfigure-metadata.properties


这个文件,获取需要自动加载的类路径进行自动装载。可以看下这个配置文件。



我这里版本是 2.1.6RELEASE。继续看看该配置文件的内容有些什么;



这个配置文件中都是需要装载的类的路径。所以这里就说明了 loadMetadata 这个方法就是加载这些配置信息,但是加载完做什么呢?所以就是下一步 getAutoConfigurationEntry 方法了,这个方法就是获得 AutoConfigurationEntry 对象。


继续看下一个方法


 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //判断是否开启注解,如果开启则返回空对象 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //获得注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); //这个方法是用来获取默认支持的自动配置类名列表 //spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath下所有jar包中 //的META_INF/spring.factories,然后将其值作为自动配置类导入到容器中,自动装配类这样就会生效了。 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去除重复的配置类,如我们自己写的starter可能存在重复 configurations = removeDuplicates(configurations); //排除我们不希望其自动装配的 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
复制代码


这个方法就是获取 AutoConfigurationEntry 对象,先回去到 configurations。我们进入这个 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;    }
复制代码



可以看到这里实际加载的就是 META-INF/spring.factories 文件,所以想要实现自动加载,需要在这个文件中进行配置。 



总结一下:


Spring Boot 底层实现自动配置的流程:


  1. Spring Boot 应用启动

  2. @SpringBootApplication 注解执行

  3. @EnableAutoConfifiguration

  4. AutoConfifigurationImportSelector.class 执行,它通过 selectImports,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 springFactory 加载器进行一系列的容器创建过程。


其实我们还有一个注解没讲,@ComponentScan 这个注解就是包扫描器,用来指定扫描器要从哪个包开始扫描。


可以参考:SpringBoot如何使用注解装配Bean


上面说了一堆,都是本文的重点,但是大家可以了解下,知道为什么 Spring Boot 不用我们在写大量的配置了,是因为 Spring Boot 在启动的时候把我们都加载好了,但是要实现这样的效果,就需要保证制定的 starter 满足一定的规范,比如必须在 starter 中的 META-INF/spring.factories 进行配置。


另外如何自定义一个 starter,请参考前面的文章:


Spring Boot 如何手写stater


推荐阅读:


笔试题:了解穷举算法吗?如何用代码实现


笔试题:代码如何实现“百钱买百鸡”?


这10道 Spring 常见面试题,你能搞定吗?


终于明白为什么要加 final 关键字了!


发布于: 2020 年 11 月 04 日阅读数: 35
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
【原创】Spring Boot终极篇《上》