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

LogicAetherPro
• 阅读 1202

关注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进去之后发现。

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

熟悉的配置文件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进去

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

发现parent有<properties>值

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

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

如何实现自动配置

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

@SpringBootApplication
public 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
@Configuration
public @interface SpringBootConfiguration {
}

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

@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

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

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

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

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

这个配置文件中都是需要装载的类的路径。所以这里就说明了 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;
    }

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

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

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

总结一下:

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 关键字了!

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
3年前
JVM系列——运行时数据区
关注“Java后端技术栈”回复“面试”获取最新资料!(https://oscimg.oschina.net/oscnet/up519e5e29f630532d07c0107d0cbb47dc.jpg)!(https://oscimg.oschina.net/oscnet/upe2de9f0c0e2f
Stella981 Stella981
3年前
Jenkins+Git+Maven+Tomcat持续集成教程
关注“Java后端技术栈”回复“面试”获取最新资料回复“加群”邀您进技术交流群Jenkins是一个开源软件项目,旨在提供一个开放易用的软件平台,使软件的持续集成变得可能。现在软件开发追求的是效率以及质量,Jenkins使得自动化成为可能!亮点1.采用shell自定义
Stella981 Stella981
3年前
JVM系列
关注Java后端技术栈”回复“面试”获取最新资料!(https://oscimg.oschina.net/oscnet/up621c8f9c4883554771f7d7310658c9e3.jpg)本文内容!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei