springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter

小Hub领读:

我觉得这是SpringBoot的比较核心的功能了,就是这个starter,自动装配,让我们可以快速集成第三方框架,真是个好设计!

太赞了,SpringBoot+Vue前后端分离完整入门教程!​mp.weixin.qq.com
springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第1张图片

作者:jack_xu
来源: https:// juejin.im/post/5ec7d2f5 e51d45785c693a9e

什么是 Spring Boot

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第2张图片

我们对着官网来翻译一下:

  • 创建独立的 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 点进去

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第3张图片

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

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

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第4张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第5张图片

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

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

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第6张图片

找到实现方法 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 等方法。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第7张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第8张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第9张图片

而这个变量的值还是 META-INF/spring.factories。

5b06bbc757dd5e982f883a1ac13bec49.png

看到这里我们很激动,于是我毫不犹豫的在项目中搜索这个文件,原来 SpringFactoriesLoader 的作用就是从 classpath/META-INF/spring.factories 文件中,根据 key 来加载对应的类到 Spring IoC 容器中。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第10张图片

看到这里小伙伴就明白了,就是把这么多 Configuration 下的 Bean 加载到容器里嘛,

但是 But

,怎么还有 RabbitMQ、Elasticsearch 这些我都用不到,怎么也给加到容器里来了,那多浪费空间和内存啊?小伙伴莫慌,于是我又带着好奇心理打开了 RabbitMQ 的配置类。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第11张图片

看到这里终于舒了口气,小伙们有没有发现这里多了一些 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。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第12张图片

需求

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

步骤

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第13张图片

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);
}
复制代码

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第14张图片

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第15张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第16张图片

5、配置类,用来读取用户的选择,作用和 @Value 一样,只是用了 jackxu.format 的前缀,这样更方便。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第17张图片

6、序列化实现类,这个就是提供给用户用来序列化用的,看名字 Template 大家也能知道,比如我们常用的 RedisTemplate、JdbcTemplate,构造函数的时候直接传入具体的实现。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第18张图片

7、好,现在就是最关键的主类了,我们从上往下看,@Import 之前说过了,导入配置类,就是将该配置类中的 Bean 注入到容器,@EnableConfigurationProperties 这是在将属性类激活,注入到容器中,也可以用 @Bean 的方式,@Configuration 说明这是一个配置类。接下来将 FormatTemplate 注入到容器中,我们看到首先是去属性类中去读属性,如果是 fastjson 就返回 fastjson 的实现,如果是 gson 就返回 gson 的实现,如果没读取到,就用前面设置的 @Primary 的默认实现。

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第19张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第20张图片

测试

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第21张图片

2、测试项目中引用自己的 starter

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第22张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第23张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第24张图片

5、启动 Spring Boot 项目

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第25张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第26张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第27张图片

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第28张图片

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

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第29张图片

springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter_第30张图片

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

后记

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

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


推荐阅读:

太赞了,SpringBoot+Vue前后端分离完整入门教程!

分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存!

Github上最值得学习的100个Java开源项目,涵盖各种技术栈!

2020年最新的常问企业面试题大全以及答案

你可能感兴趣的:(springboot入参检查aop_深度解析 Spring Boot 以及手写一个 starter)