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

什么是 Spring Boot

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

深度解析 Spring Boot 以及手写一个 starter_第1张图片
我们对着官网来翻译一下:

  • 创建独立的 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 点进去
深度解析 Spring Boot 以及手写一个 starter_第2张图片
可以看到它实际上是一个复合注解,上面四个是元注解,下面三个才是重点

  1. @Configuration
  2. @EnableAutoConfiguration
  3. @ComponentScan

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

深度解析 Spring Boot 以及手写一个 starter_第3张图片

@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 也不例外,我们点进去发现如红框所示。

深度解析 Spring Boot 以及手写一个 starter_第4张图片
@Import 注解是什么意思呢? 它对应 XML 形式下的< import resource/ >,就是导入资源,把多个分布在不同容器下的配置合并在一个配置中。@Import 注解可以配置三种不同的 class :

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

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

深度解析 Spring Boot 以及手写一个 starter_第5张图片
找到实现方法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 等方法。

深度解析 Spring Boot 以及手写一个 starter_第6张图片
那具体这些 Bean 从哪里找呢,我们将 getCandidateConfigurations 方法点进去,发现了一个惊天秘密,那就是在这有一个 META-INF/spring.factories 文件。

深度解析 Spring Boot 以及手写一个 starter_第7张图片
当然这是一个报错信息,我们不敢断定就是这里,没关系,我们把 SpringFactoriesLoader.loadFactoryNames 点进去,发现这里有个变量 FACTORIES_RESOURCE_LOCATION。

深度解析 Spring Boot 以及手写一个 starter_第8张图片
而这个变量的值还是 META-INF/spring.factories。

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

深度解析 Spring Boot 以及手写一个 starter_第10张图片
看到这里终于舒了口气,小伙们有没有发现这里多了一些 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。
深度解析 Spring Boot 以及手写一个 starter_第11张图片

需求

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

步骤

1、 创建一个Spring Boot项目,这里项目名字叫 jackformat-spring-boot-starter
深度解析 Spring Boot 以及手写一个 starter_第12张图片

2、引入依赖

      
          org.springframework.boot
          spring-boot-autoconfigure
      

    
      
          org.springframework.boot
          spring-boot-configuration-processor
          true
      
      
      
          com.alibaba
          fastjson
          1.2.56
      

      
          com.google.code.gson
          gson
          2.2.4
      

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

public interface FormatProcessor {

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

深度解析 Spring Boot 以及手写一个 starter_第13张图片

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

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

深度解析 Spring Boot 以及手写一个 starter_第18张图片
8、最后一步最关键的就是设置,在 resources 文件夹下创建 META-INF/spring.factories 文件,通过上面的知识,Spring Boot 在启动的时候就是读取该文件下的配置类,从而将 Bean 加载到容器中。

深度解析 Spring Boot 以及手写一个 starter_第19张图片

测试

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

深度解析 Spring Boot 以及手写一个 starter_第20张图片
2、测试项目中引用自己的 starter

深度解析 Spring Boot 以及手写一个 starter_第21张图片

3、写一个controller,一个测试类,并把 formatTemplate 注入进来
深度解析 Spring Boot 以及手写一个 starter_第22张图片

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

深度解析 Spring Boot 以及手写一个 starter_第23张图片
5、启动Spring Boot 项目

深度解析 Spring Boot 以及手写一个 starter_第24张图片
6、通过浏览器访问,发现这里显示的是 fastjson 方式的序列化,成功了!

深度解析 Spring Boot 以及手写一个 starter_第25张图片
7、在测试 gson 的方式,返回了默认 gson 的实现,也成功了!

深度解析 Spring Boot 以及手写一个 starter_第26张图片

深度解析 Spring Boot 以及手写一个 starter_第27张图片
8、最后测试用户不选择的情况下,默认使用 fastjson,圆满成功!

深度解析 Spring Boot 以及手写一个 starter_第28张图片

深度解析 Spring Boot 以及手写一个 starter_第29张图片
至此,本个需求已经成功做出来了,我力求在做的过程中将手写一个 starter 所需用到的技术都讲到,串联起来,希望大家喜欢!

后记

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

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

你可能感兴趣的:(Spring,Boot)