Spring Boot Starter 非权威指南
ShawJie

Spring Boot Starter 非权威指南

本篇内容算是对之前的Spring Boot自动装配装配原理做一个实践性的总结,结合在实际开发过程中对于Starter模块化的使用,算是给Spring Boot自动装配系列画上一个句号。

​ 关于Spring Boot Starter想必大家都不陌生,在实际项目开发中,使用到Spring Boot应用或多或少都引入了一些应用功能相关所需求的Starter,譬如spring-boot-starter-webspring-boot-starter-data-jpa等等。在Spring Boot官方文档的特性描述中就有如此一句:Provide opinionated 'starter' dependencies to simplify your build configuration(提供启动依赖模块以简化构建项目的配置)。作为Spring Boot的宣传点stand-alone & production-grade,starter是不可或缺的重要基石之一。

Starter模块开发

​ 关于Starter的模块结构目前没有一个强制性的规定,可以使用单模块开发,也可以使用依赖包(即starter包 + autoconfiguration包)模式进行开发。我个人对于Starter的性质理解更像是一种插件,就像Spring官方在描述里所说的一样,目的是简化构建项目构建的配置,提升开发效率。所以Starter应该是模块化的,可拔插的,对于项目核心部分来说使用是无需关心内部逻辑的。目前生产应用中的Spring-boot项目从starter的角度来看是这样的。

image

单模块Starter

​ 单模块Starter结构较为简单,一般不需要做额外的拆分,在模块的代码目录中创建一个@Configuration类,在资源目录中创建META-INF/spring.factories文件即可完成一个最简易starter的定义。为什么通过这两者能完成spring-starter的自动装配,其中的原理在之前的spring boot自动装配原理已经提到过,于此不再赘述。

DemoAutoConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class DemoAutoConfiguration {

@Bean
public HelloService helloService() {
return new HelloService();
}

public static class HelloService {
public String sayHello() {
return "hello world";
}
}
}

spring.facoties

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.shawjie.demo.DemoAutoConfiguration

​ 在完成以上工作之后,在项目中进行starter的引用。

pom.xml

1
2
3
4
5
<dependency>
<groupId>com.shawjie</groupId>
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

​ 在主应用中使用starter内的功能。

1
2
3
4
5
6
7
@Autowired
private DemoAutoConfiguration.HelloService helloService;

@GetMapping("/test")
public String getHelloWorld() {
return helloService.sayHello();
}
1
2
curl -X GET localhost:8080/test
// hello world

​ 自此一个简单的单模块spring boot starter的开发到使用就已经结束。其中注册、自动装配的过程全部由Spring boot autoconfiguration完成了,我们只需要按照约定进行对于内容的开发即可,也符合Spring boot应用约定大于配置的基本原则。

依赖包Starter

​ 依赖包Starter模式是目前第三方应用Starter所采用比较多的模式,和单模块Starter的区别在于使用者引入的starter包内只有pom依赖,无实际代码,而pom依赖中的autoconfiguration才是starter的功能核心,负责相关@Bean装配以及逻辑处理的过程。

​ 以下是mybatis-spring-boot-starter的依赖树,依赖结构算是第三方starter模块中比较经典的,大致的模块依赖结构如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ mybatis-spring-boot-starter ---
[INFO] org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.1.2
[INFO] +- org.springframework.boot:spring-boot-starter:jar:2.2.5.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:2.2.5.RELEASE:compile
[INFO] | | \- org.springframework:spring-context:jar:5.2.4.RELEASE:compile
[INFO] | | +- org.springframework:spring-aop:jar:5.2.4.RELEASE:compile
[INFO] | | \- org.springframework:spring-expression:jar:5.2.4.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.2.5.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:2.2.5.RELEASE:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.12.1:compile
[INFO] | | | \- org.apache.logging.log4j:log4j-api:jar:2.12.1:compile
[INFO] | | \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
[INFO] | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] | +- org.springframework:spring-core:jar:5.2.4.RELEASE:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.2.4.RELEASE:compile
[INFO] | \- org.yaml:snakeyaml:jar:1.25:runtime
[INFO] +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.2.5.RELEASE:compile
[INFO] | +- com.zaxxer:HikariCP:jar:3.4.2:compile
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.30:compile
[INFO] | \- org.springframework:spring-jdbc:jar:5.2.4.RELEASE:compile
[INFO] | +- org.springframework:spring-beans:jar:5.2.4.RELEASE:compile
[INFO] | \- org.springframework:spring-tx:jar:5.2.4.RELEASE:compile
[INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.1.2:compile
[INFO] +- org.mybatis:mybatis:jar:3.5.4:compile
[INFO] \- org.mybatis:mybatis-spring:jar:2.0.4:compile

​ 从依赖树我们可以看出,mybatis-spring-boot-starter依赖了spring-boot-starter以保证引用目标对象持有最基础的spring-boot自动装配能力,而mybatis-spring-boot-autoconfigure实现了mybatis对于Spring-boot应用的支持(包括SqlSession的装配、MapperScanner的注册等等),mybatis-spring实现了对spring的功能支持,mybatis则负责了基础的能力实现。

​ 依赖树的可读性不高,我们可以转换成图来看。

image

Starter的兄弟

@Conditional

​ 关于@Conditional注解的内容在Spring boot 自动装配原理中也有较为详尽的分析和描述,于此对原理方面的内容也就不打算过多深究了,感兴趣的朋友可以回看一下之前的内容。

​ 在实际Starter模块开发过程中,经常会遇到starter所依赖的类、Spring Bean、配置不存在的情况,于此我们也就不再需要Spring自动装配功能对starter的配置进行解析处理。Spring也提供了以@Conditional作为元注解的衍生注解以完成以上需求,由于衍生注解较多,于此用表格进行展示。

注解描述
@ConditionalOnProperty该注解用于对应用配置项进行检查,使用prefix进行注解的前缀匹配,用havingVal以检查配置项是否存在,用matchIfMissing用于应对配置不存在采用默认值的情况
@ConditionalOnResource用于检查指向资源是否存在,若不存在则自动配置类不会被加载
@ConditionalOnClass/@ConditionalOnMissingClassOnClassOnMissingClass是相对的,用于检查classpath下类是否存在或是否不存在,检查方式是通过asm读取类元信息,而后通过classloader对目标类进行加载,若加载成功则说明类存在,反之则不存在,具体逻辑在Spring boot自动装配系列中也有提到
@ConditionalOnBean/@ConditionalOnMissingBeanOnClass类似,该注解用于检查Bean是否存在或不存在于Spring容器中
@ConditionalOnWebApplication/@ConditionalOnNotWebApplication用于检查当前应用类型是否为WEB应用(可细化到检查是否是Servlet应用或Reactive应用)
@ConditionalOnExpression检查是否包含SpringEL的结果

​ 其实基于@Conditional注解的衍生还不止这些,@ConditionalOnJava用于判断Jvm所运行的Java版本,@ConditionalOnJndi用于检查目标资源是否存在等等,甚至说你也可以实现自己的@Conditional衍生注解,只需要实现自己的Condition类即可。

@ConfigurationProperties

​ 在你为你的Starter定义了一套配置属性时,通过@Value注解取值的效率往往太低了,此时不妨试一试@ConfigurationProperties,让Spring将注解转换成你的Properties类,简化配置内容值的获取步骤,提高获取效率。注意,@ConfigurationProperties@Value的功能是冲突的,当在@ConfigurationProperties标注的类中使用@Value@Value会进行值的覆盖。

小结

​ 在实际开发过程中,对于泛用性较高的功能,譬如用户中心、数据查询分页、数据中台等等功能,都可以采用Starter的开发模式,以减少主应用的重复开发,同时也提高模块的复用率。Spring Cloud的系列微服务,在本质上也都是一个个的Starter插件,在进行少量的配置工作后提供强有力的功能。

​ 当然在这里要小提一句,Spring官方不建议第三方的模块Starter命名以spring-boot-starter-xxx进行,该命名方式通常适用于spring-boot的官方Starter,在必要的情况下,官方会使用对你的自动装配特性进行支持。而建议的命名方式为xxx-spring-boot-starter,如mybatis-spring-boot-starter

尾巴

​ 最近对于自己的学习效率还是不太满意,之前许下的一个月两篇质量更新也已经连续两个月没有完成。对自己也有些稍许失望,但是在新的工作上也慢慢步入了正轨,希望…砥砺前行吧。以上,加油。