专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

深度解析 Spring Boot 以及手写一个 starter

什么是 Spring Boot

Spring Boot 基本上是 Spring 框架的扩展,它消除了设置 Spring 应用程序所需的复杂例行配置。我们在使用 Spring 框架的时候,我们接触得比较多的应该是 Spring MVC、 IOC 、 DI 、AOP 等等,而这些框架在使用的过程中会需要配置大量的 XML,或者需要做很多繁琐的配置。Spring Boot 可以帮助我们快速搭建一个基于 Spirng 框架以及 Spring 生态体系的应用解决方案。

58_1.png我们对着官网来翻译一下:

  • 创建独立的 Spring 应用程序
  • 直接嵌入Tomcat,Jetty 或 Undertow(无需部署 WAR 文件),java -jar 就可以运行
  • 提供 starter 依赖来简化你自己的配置
  • 自动装配 Spring 和第三方的依赖只要可能
  • 提供可用于生产的功能,例如指标,运行状况检查和外部化配置,比如 Actuator
  • 完全没有代码生成,也不需要 XML 配置

看了上面这么多主要有两点,约定大于配置自动装配

约定大于配置

约定优于配置的体现主要是

1、 maven 的目录结构,默认有 resources 文件夹存放配置文件,默认打包方式为 jar
2、 spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的 tomcat 容器,使得构建一个 web 应用更加简单
3、 默认提供 application.properties/yml 文件
4、 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
5、 EnableAutoConfiguration 默认对于依赖的 starter 进行自动装配

自动装配

讲自动装配首先从注解开始,我们从 @SpringBootApplication 点进去

58_2.png可以看到它实际上是一个复合注解,上面四个是元注解,下面三个才是重点

1、 @Configuration
2、 @EnableAutoConfiguration
3、 @ComponentScan

我们可以直接用这三个注解也可以启动 Spring Boot 应用,只是每次配置三个注解比较繁琐,所以直接用一个复合注解更方便些。下面是官网的截图,刚兴趣的小伙伴可自行翻译。

58_3.png

@Configuration

@Configuration 这个注解大家应该都用过,它是 JavaConfig 形式的基于 Spring IOC 容器的配置类使用的一种注解。所以在启动类里面标注了 @Configuration,意味着它其实也是一个 IoC 容器的配置类。

传统意义上的 Spring 应用都是基于 xml 形式来配置 bean 的依赖关系。但是从 Spring3 开始,Spring 就支持了两种 bean 的配置方式,一种是基于 xml 文件方式,另一种就是 JavaConfig,任何一个标注了@Configuration 的 Java 类定义都是一个JavaConfig 配置类。而在这个配置类中,任何标注了@Bean 的方法,它的返回值都会作为 Bean 定义注册到 Spring 的 IoC 容器,方法名默认成为这个 Bean 的 id。然后通过 spring 容器在启动的时候,把 Bean 进行初始化并且,如果 Bean 之间存在依赖关系,则分析这些已经在 IoC 容器中的 Bean 根据依赖关系进行组装。

@ComponentScan

@ComponentScan 这个注解大家也用过,这个很简单,就是扫包,相当于 xml 配置文件中的
< context:component-scan > 。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自 动装配到 Spring 的 IoC 容器中。

标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类。(注:@Repository、@Service、@Controller 的底层还是 @Component)。 ComponentScan 默认会扫描当前 package 下的的所有加了相关注解标识的类到 IoC 容器中。

@EnableAutoConfiguration

好,主角登场了,@EnableAutoConfiguration 是 Spring Boot 的灵魂,是重中之重。从 Spring3.1 开始,提供了一系列的 @Enable 开头的注解,它是在 JavaConfig 框架上更进一步的完善,使用户在使用 Spring 相关的框架避免配置大量的代码从而降低使用的难度。

比如常见的一些 Enable 注解:@EnableWebMvc、@EnableScheduling、@EnableAsync 等等。 每一个涉及到 Enable 开头的注解,都会带有一个 @Import 的注解, @EnableAutoConfiguration 也不例外,我们点进去发现如红框所示。

58_4.png@Import 注解是什么意思呢? 它对应 XML 形式下的< import resource/ >,就是导入资源,把多个分布在不同容器下的配置合并在一个配置中。@Import 注解可以配置三种不同的 class :

1、 普通 Bean 或者带有 @Configuration 的配置文件
2、 实现 ImportSelector 接口进行动态注入
3、 实现 ImportBeanDefinitionRegistrar 接口进行动态注入

这里导入的是第二种 importSelector,这是一种动态注入 Bean 的技术,我们把AutoConfigurationImportSelector 点进去,发现它实现了 ImportSelector 接口。

58_5.png找到实现方法selectImports ,该方法的作用就是找到相应的 Bean 注入到容器中。

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

再从 getAutoConfigurationEntry 方法点进去,这里面做了许多事情,就是把找到的 Bean 进行排除、过滤、去重,我们可以看到 removeDuplicates、remove、filter 等方法。

58_6.png那具体这些 Bean 从哪里找呢,我们将 getCandidateConfigurations 方法点进去,发现了一个惊天秘密,那就是在这有一个 META-INF/spring.factories 文件。

58_7.png当然这是一个报错信息,我们不敢断定就是这里,没关系,我们把 SpringFactoriesLoader.loadFactoryNames 点进去,发现这里有个变量 FACTORIES_RESOURCE_LOCATION。

58_8.png而这个变量的值还是 META-INF/spring.factories。 58_9.png看到这里我们很激动,于是我毫不犹豫的在项目中搜索这个文件,原来 SpringFactoriesLoader 的作用就是从 classpath/META-INF/spring.factories 文件中,根据 key来加载对应的类到 Spring IoC 容器中。 58_10.png看到这里小伙伴就明白了,就是把这么多Configuration下的 Bean 加载到容器里嘛, 但是But,怎么还有 RabbitMQ、Elasticsearch这些我都用不到,怎么也给加到容器里来了,那多浪费空间和内存啊?小伙伴莫慌,于是我又带着好奇心理打开了 RabbitMQ 的配置类。

58_11.png看到这里终于舒了口气,小伙们有没有发现这里多了一些 Conditional 的注解,其实这些就是条件注解,Spring Boot 也不傻,它会发现如果当前的 classpath 环境下没有相关联的依赖,则意味着这些类没必要进行加载。所以,通过这种条件过滤可以有效的减少 @configuration 类的数量从而降低 Spring Boot 的启动时间。

Conditions 描述
@ConditionalOnBean 在存在某个 bean 的时候
@ConditionalOnMissingBean 不存在某个 bean 的时候
@ConditionalOnClass 当前 classpath 可以找到某个类型的类时
@ConditionalOnMissingClass 当前 classpath 不可以找到某个类型的类时
@ConditionalOnResource 当前 classpath 是否存在某个资源文件
@ConditionalOnProperty 当前 jvm 是否包含某个系统属性为某个值
@ConditionalOnWebApplication 当前 spring context 是否是 web 应用程序

好,有了上面这么多预备知识后,就可以开始手写一个我们自己的 starter 了。

手写 starter

starter 工程的命名

starter 是一个开箱即用的组件,减少不必要的重复代码,重复配置。例如,如果要使用 Spring 和 JPA 进行数据库访问,在项目中引用spring-boot-starter-data-jpa 即可。

Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name},例如 spring-boot-starter-web。非官方 starter 命名应遵循 {name}-spring-boot-starter 的格式,例如,dubbo-spring-boot-starter。

58_12.png

需求

写一个序列化的插件,并且可以自由的选择 fastjson 还是 gson,如果没选的情况下默认选择fastjson。

步骤

1、 创建一个Spring Boot项目,这里项目名字叫 jackformat-spring-boot-starter

58_13.png

2、引入依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-autoconfigure</artifactId>
      </dependency>

    <!-- 这个是用来提示用的-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
      </dependency>

      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.56</version>
      </dependency>

      <dependency>
          <groupId>com.google.code.gson</groupId>
          <artifactId>gson</artifactId>
          <version>2.2.4</version>
      </dependency>

3、先定义一个格式化的接口,分别写两个实现类

public interface FormatProcessor {

    /**
     * 定义一个格式化的方法
     *
     * @param obj
     * @param <T>
     * @return
     */
    <T> String format(T obj);
}

58_14.png

58_15.png4、写一个配置类,这里用了条件注解,如果 fastjson 和 gson 类存在的情况下才加载对应的实现类,因为在 pom 文件里都引用了,所以这里都会被装载。注意这里红框标的 @Primary,对同一个接口,有几种不同的实现类时,@Autowired 是按类型注入的,不知道要选哪一个,按照第二点需求,用户在没选的情况下默认选择 fastjson,所以这里给 fastjson 的实现上打上 @Primary。 58_16.png5、配置类,用来读取用户的选择,作用和 @Value 一样,只是用了 jackxu.format 的前缀,这样更方便。

58_17.png6、序列化实现类,这个就是提供给用户用来序列化用的,看名字 Template 大家也能知道,比如我们常用的 RedisTemplate、JdbcTemplate,构造函数的时候直接传入具体的实现。 58_18.png7、好,现在就是最关键的主类了,我们从上往下看,@Import 之前说过了,导入配置类,就是将该配置类中的 Bean 注入到容器,@EnableConfigurationProperties 这是在将属性类激活,注入到容器中,也可以用 @Bean 的方式,@Configuration 说明这是一个配置类。接下来将 FormatTemplate 注入到容器中,我们看到首先是去属性类中去读属性,如果是 fastjson 就返回 fastjson 的实现,如果是 gson 就返回 gson 的实现,如果没读取到,就用前面设置的 @Primary 的默认实现。

58_19.png8、最后一步最关键的就是设置,在 resources 文件夹下创建 META-INF/spring.factories 文件,通过上面的知识,Spring Boot 在启动的时候就是读取该文件下的配置类,从而将 Bean 加载到容器中。

58_20.png

测试

1、将自己的 starter 项目进行 install 打包

58_21.png2、测试项目中引用自己的 starter

58_22.png

3、写一个controller,一个测试类,并把 formatTemplate 注入进来

58_23.png

4、设置我们需要制定的序列化方式,这里选用 fastjson

58_24.png5、启动Spring Boot 项目

58_25.png6、通过浏览器访问,发现这里显示的是 fastjson 方式的序列化,成功了!

58_26.png7、在测试 gson 的方式,返回了默认 gson 的实现,也成功了!

58_27.png

58_28.png8、最后测试用户不选择的情况下,默认使用 fastjson,圆满成功!

58_29.png

58_30.png至此,本个需求已经成功做出来了,我力求在做的过程中将手写一个 starter 所需用到的技术都讲到,串联起来,希望大家喜欢!

后记

小伙伴们,Spring Boot 的使用中极大的简化了我们的使用,我也是14年毕业就开始用 SSM,那时候各种配置各种依赖,各种 XML 很是恶心。但是现在有了 Spring Boot 在几分钟内就可以让我们快速搭建一个项目跑起来,时代在变迁,也感谢让我们越来越便利,在便利的过程中,我们还是要对底层的原理有一点的了解,而不是浮在上面只会使用,否则一但有问题找起来也不方便。

本文带大家对 Spring Boot 底层入了个门,还有 SringApplication 实例创建,设置初始化器和监听器,以及 run 方法里干的一些事,内置 Tomcat 如何实现的,由于篇幅有限都没有讲,留给小伙伴自行研究。最后原创不易,如果觉得写得不错,请点一个赞

文章永久链接:https://tech.souyunku.com/37376

未经允许不得转载:搜云库技术团队 » 深度解析 Spring Boot 以及手写一个 starter

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们