一、简介
SpringBoot由众多的starter组成,这些starter也被称为是场景启动器,在工程中引入特定的starter再进行少量的配置就可以使用其提供的相应的功能了,SpringBoot在不断的维护和扩展不同场景的starter给使用者提供更完善的功能,我们也可以创建自定义的starter制定我们自己的特定场景。
二、SpringBoot中的starter
-
我们查看SpringBoot提供的starter可以发现所有的starter包下都没有任何代码
-
我们找到
spring-boot-starter-data-redis.jar
包的pom.xml查看一下内容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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.6.RELEASE</version>
<name>Spring Boot Data Redis Starter</name>
<!-- 省略一部分 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.2.6.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>我们看到这个starter继承自
spring-boot-starters
包,并且所有的starter都引入了spring-boot-starter
包,包中还可以根据需要选择性的引入其他的jar包。 -
进入到
spring-boot-starter
包中,可以找到它引入了spring-boot-autoconfigure
包,这是我们完成自动配置的关键1
2
3
4
5
6<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.6.RELEASE</version>
<scope>compile</scope>
</dependency>查看
spring-boot-autoconfigure.jar
的目录结构发现包含了各种starter的配置信息。- 标1:需要自动配置的类声明文件
- 标2:配置文件中自动提示的元属性数据声明文件
- 标3:自动配置控制类
- 标4:场景属性类
以上就是一个starter的关键所在了,总结下来就是:
- 启动器starter只是用来做依赖管理的,其不应该包含任何代码和配置,需要引入autoconfigure包
- 自动装配autoconfigure包,需要包含我们需要让SpringBoot自动装配的模块,以及资源配置信息,总的来说包括:spring.factories、spring-configuration-metadata.json、XxxAutoConfiguration、XxxProperties
- 使用的时候只需要引入启动器starter就可以实现自动配置
三、自定义starter命名规范
- 官方命名规范
- 规则:spring-boot-starter-模块名
- 举例:spring-boot-starter-data-redis、spring-boot-starter-web
- 自定义命名规范
- 规则:模块名-spring-boot-starter
- 举例:cc-spring-boot-starter
四、自定义starter demo
根据上面的描述,我们需要创建两个项目:cc-spring-boot-autoconfigure和cc-spring-boot-starter
-
创建cc-spring-boot-autoconfigure工程
需要注意的是在工程创建完成之后,要删除启动类、application.properties和test文件夹
-
pom.xml和控制类
-
pom.xml
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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<groupId>cc.lu</groupId>
<artifactId>cc-spring-boot-autoconfigure</artifactId>
<version>1.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project> -
CcProperties
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package cc.lu.autoconfigure.wt;
import org.springframework.boot.context.properties.ConfigurationProperties;
"cc.config") (prefix =
public class CcProperties {
private String name = "cc";
private Integer age = 3;
private String birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String toString() {
return "CcProperties{" + "name='" + name + '\'' + ", age=" + age + ", birthday='" + birthday + '\'' + '}';
}
}类的有些属性被设置了值,这些值就是当配置文件未配置属性时使用的默认值,这就是参数配置设置默认值的一种方式。还有一种,继续往下看
-
CcService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package cc.lu.autoconfigure.wt;
public class CcService {
private CcProperties ccProperties;
public void setCcProperties(CcProperties ccProperties) {
this.ccProperties = ccProperties;
}
public String info() {
return ccProperties.toString();
}
}一个普通的类,没啥可说的,继续往下
-
CcAutoConfiguration
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
28package cc.lu.autoconfigure.wt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
false) (proxyBeanMethods =
({ CcService.class })
({ CcProperties.class })
public class CcAutoConfiguration {
private CcProperties ccProperties;
({ CcService.class })
public CcService ccService() {
CcService ccService = new CcService();
// 打个日志查看方法是否被调用
System.out.println("------auto register!------");
ccService.setCcProperties(ccProperties);
return ccService;
}
}CcAutoConfiguration的声明上使用了@ConditionalOnClass和@EnableConfigurationProperties做加载控制,然后在类中创建了CcService的实例,并且使用
@ConditionalOnMissingBean({ CcService.class })
来控制创建条件。
-
-
spring.factories和spring-configuration-metadata.json配置
-
spring.factories
1
2\ =
cc.lu.autoconfigurer.wt.CcAutoConfiguration将CcAutoConfiguration放给EnableAutoConfiguration,告知SpringBoot启动的时候进行检测加载。
-
spring-configuration-metadata.json元属性数据配置
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
28
29{
"groups": [
{
"name": "cc.config",
"type": "cc.lu.autoconfigure.wt.CcProperties",
"sourceType": "cc.lu.autoconfigure.wt.CcProperties"
}
],
"properties": [
{
"name": "cc.config.age",
"type": "java.lang.Integer",
"sourceType": "cc.lu.autoconfigure.wt.CcProperties",
"defaultValue": 3
},
{
"name": "cc.config.birthday",
"type": "java.lang.String",
"sourceType": "cc.lu.autoconfigure.wt.CcProperties"
},
{
"name": "cc.config.name",
"type": "java.lang.String",
"sourceType": "cc.lu.autoconfigure.wt.CcProperties",
"defaultValue": "cc"
}
],
"hints": []
}这个文件的目的是在properties或yml文件中配置自定义属性的时候,可以自动提示,例如:
这个文件看上去好像非常复杂,尤其是当我们有几十个自定义配置属性的时候,难道要一个个属性的去写吗?答案是当然不用,可以通过引入一个jar包来在打包的时候自动生成:
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>引入之后,执行完命令
mvn clean package
之后,到target/classes/META-INF目录下查看自动生成的文件
-
至此,我们的cc-spring-boot-autoconfigure就创建完成了,回瞄一眼,是不是和官方的autoconfigure包含的文件一致了。
-
-
创建cc-spring-boot-starter工程
一个很普通的maven工程,因为starter中一般不包含任何代码,仅仅作为包的依赖管理工程,所以创建完成之后依然要删除启动类、application.properties和test文件夹,它只有一个pom.xml
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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cc.lu</groupId>
<artifactId>cc-spring-boot-starter</artifactId>
<version>1.0</version>
<name>cc-spring-boot-starter</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>cc.lu</groupId>
<artifactId>cc-spring-boot-autoconfigure</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project> -
创建cc-starter-demo测试工程
创建一个常规的SpringBoot工程,引入我们自定义的starter,并设置相关的配置信息,就可以使用了。
-
pom.xml
1
2
3
4
5<dependency>
<groupId>cc.lu</groupId>
<artifactId>cc-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency> -
application.properties
修改birthday和name两个属性,age使用默认值,待会看效果
1
22017-03-09 =
yc = -
创建一个Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package cc.lu.starter.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import cc.lu.autoconfigurer.wt.CcService;
public class PersonController {
private CcService ccService;
"/info") (
public void info() {
System.out.println(ccService.info());
}
}
-
五、测试
1. 情况一
-
启动cc-starter-demo工程,查看启动日志,会发现我们在CcAutoConfiguration中打印的字符串
------auto register!------
出现在了控制台,说明CcService的实例创建是在CcAutoConfiguration中完成的。 -
访问http://127.0.0.1:8080/info,查看控制台的日志,看到我们在application.properties文件中配置的值生效了,也就是我们的starter写的正常。
2. 情况二
-
在启动类中重新定义CcService的实例创建方法
1
2
3
4
5
6
7
8
public CcService ccService(CcProperties ccProperties) {
CcService ccService = new CcService();
// 打个日志查看方法是否被调用
System.out.println("====client register====");
ccService.setCcProperties(ccProperties);
return ccService;
} -
启动工程,查看控制台是打印了字符串
====client register====
还是------auto register!------
控制台打印的是我们demo工程里创建CcService实例方法内的字符串,而CcAutoConfiguration内的打印语句未执行,这是因为我们在CcAutoConfiguration类中的方法加了
@ConditionalOnMissingBean({ CcService.class })
来控制实例的创建。(划知识点,约定大于配置)
六、总结
通过自定义starter分析一下其工作原理:
- SpringBoot在启动时扫描项目依赖的全部jar包,并寻找
META-INF/spring.factories
文件 - 根据
META-INF/spring.factories
加载符合条件的AutoConfigure
类 - 根据
@Conditional
注解条件进行自动配置,并将Bean注入到Spring Context中。