在前面两章一、SpringBoot入门 、二、SpringBoot自动配置原理 的学习后,我们对如何创建一个SpringBoot项目、SpringBoot的运行原理以及自动配置等都有了一定的了解。如果我们系统中也想要拥有自动配置的功能,可以自己编写一个starter (启动器),想想就觉得很酷,因为这意味着我们不仅有自己定义的自动配的功能,而且具有更通用的耦合度更低的配置。
   还是以第一章开头的简单功能为例:浏览器发送sayHello请求,服务器接受请求并处理,响应Hello 。
   首先我们看下工程结构:

图片
helloworld-spring-boot-starter-autoconfigure(以下简称autoconfigure):该模块用来实现Helloworld的自动配置功能,它的打包方式为jar;
helloworld-spring-boot-starter(以下简称starter):该模块的打包方式是jar,依赖autoconfigure模块,它不写任何代码,只做自动配置包的自动引入,如果要使用helloworld自动配置功能,只要在pom文件中引入starter模块即可:

1
2
3
4
5
<dependency>
<groupId>com.seagetech.spring.boot</groupId>
<artifactId>helloworld-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

1 项目工程的创建

1.1 创建Empty Project

(1)、新建一个空的项目,如下图,点击Next
图片

(2)、输入项目名称以及选择项目存放路径,点击Finish

图片

1.2 创建starter模块

(1)、在1.1节中新建的空项目基础上新建一个Module

图片

(1)、选择Maven,点击Next

图片

(2)、输入GroupId、ArtifactId和Version信息,点击Finish

图片

(3)、由于这个模块只做自动配置包的引入,所有删除src下的包,最终项目结构如下:

图片

pom.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.seagetech.spring.boot</groupId>
<artifactId>helloworld-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>


</project>

1.3 创建autoconfigure模块

(1)、按1.2节第(1)步,新建一个Module,并选择Spring Initializr,点击Next

图片

(2)、输入项目相关信息,如下图所示:

图片

(3)、选择依赖,我们这里只选择web场景的依赖,点击Next

图片

(4)、默认,不需要改动,点击Finish

图片

(4)、删除SpringBoot自动创建的主配置类、resources下所有文件(夹)以及test文件夹,最终项目结构如下:
图片

pom.xml(已删除test测试模块的自动配置包以及build下的maven插件)

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
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.seagetech.spring.boot</groupId>
<artifactId>helloworld-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>helloworld-spring-boot-starter-autoconfigure</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<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-web</artifactId>
</dependency>
</dependencies>
</project>

(5)、在1.2节创建的helloworld-spring-boot-starter的pom下模块引入本节创建的autoconfigure模块:

1
2
3
4
5
<dependency>
<groupId>com.seagetech.spring.boot</groupId>
<artifactId>helloworld-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

1.4 实现HelloWorld自动配置

(1)、在helloworld包下新建HelloWorldProperties类,作为配置类,目的是后面使用这个自动配置,直接可以在application.properties文件中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.seagetech.spring.boot.helloworld;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* HelloWorld属性配置类
* @auther: wangzb
* @date: 2018/11/13 22:21
*/
@ConfigurationProperties(prefix = "hello")
public class HelloWorldProperties {
/**
* 打招呼的内容,默认为“World!”
*/
private String msg = "World!";

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}
}

(2)、新建HelloWorldService接口,并实现:

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
接口:
/**
* @auther: wangzb
* @date: 2018/11/13 22:28
*/
public interface HelloWorldService {
/**
* 打招呼方法
* @param name 人名,向谁打招呼使用
* @return
*/
String sayHello(String name);
}
实现类:
/**
* @auther: wangzb
* @date: 2018/11/13 22:33
*/
@Component
public class HelloWorldServiceImpl implements HelloWorldService{

@Autowired
private HelloWorldProperties helloWorldProperties;

/**
* 打招呼方法
*
* @param name 人名,向谁打招呼使用
* @return
*/
@Override
public String sayHello(String name) {
return "Hello " + name + " " + helloWorldProperties.getMsg();
}
}

(3)、新建HelloWorldAutoConfiguration配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @auther: wangzb
* @date: 2018/11/13 22:37
*/
//定义为配置类
@Configuration
//在web条件下成立
@ConditionalOnWebApplication
//启用HelloWorldProperties配置功能,并加入到IOC容器中
@EnableConfigurationProperties({HelloWorldProperties.class})
//导入HelloWorldService组件
@Import(HelloWorldServiceImpl.class)
public class HelloWorldAutoConfiguration {
}

1.5 创建spring.factories文件

在二、SpringBoot自动配置原理 中,我们提到@EnableAutoConfiguration 的关键功能是使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包,这样我们的自动配置类才能生效,所以我们在autoconfigure模块的resources下创建META-INF/spring.factories文件:

图片

在spring.factories中写入:
1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.seagetech.spring.boot.helloworld.HelloWorldAutoConfiguration

2 helloworld自动配置的使用

创建好helloworld自动配置项目后,接下来就是使用了,我们在idea中使用install将1节中创建好的starter/autoconfigure模块打包到本地maven创库,然后在一、SpringBoot入门 章节创建的sping-boot-demo项目中使用,在其pom文件中引入:
1
2
3
4
5
<dependency>
<groupId>com.seagetech.spring.boot</groupId>
<artifactId>helloworld-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

在主配置类路径下创建HelloWorldController :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.seagetech.springbootdemo;

import com.seagetech.spring.boot.helloworld.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @auther: wangzb
* @date: 2018/11/17 11:17
*/
@Controller
public class HelloWorldController {

@Autowired
private HelloWorldService helloWorldService;

@RequestMapping(value = "/sayHello")
public String sayHello(String name){
return helloWorldService.sayHello(name);
}
}
启动项目,在浏览器输入:[http://localhost:8080/sayHello?name=wangzb](http://localhost:8080/sayHello?name=wangzb),浏览器返回:

Hello wangzb World!

看到默认返回的消息是World!,如果在application.properties文件中配置如下属性:
1
hello.msg = 你好
我们在重启项目,重复以上步骤,浏览器响应:

Hello wangzb 你好

3 元数据的配置

到目前为止,helloworld的自动配置功能已实现,并且正确使用了,但还有一点不够完美,如果你也按上面步骤实现了自己的helloworld自动配置,在application.properties中配置hello.msg属性时,你会发现并没有提示你有关该配置的信息,但是如果你想配置tomcat端口时,输入server.port是有提示的:

图片

这种功能我们如何做呢,我们打开一、SpringBoot入门 章节下载的《spring-boot-reference.pdf》文件,在目录中找到“Appendix B. Configuration Metadata(附录B.配置元数据)”,下面我们使用谷歌来翻译下相关原文:

Spring Boot jars include metadata files that provide details of all supported configuration properties.The files are designed to let IDE developers offer contextual help and “code completion” as users are working with application.properties or application.yml files.
Spring Boot jar包含元数据文件,提供所有支持的配置属性的详细信息。这些文件旨在让IDE开发人员使用application.properties或application.yml文件像用户一样提供上下文帮助和“代码完成”

……

B.1 Metadata Format
Configuration metadata files are located inside jars under META-INF/spring-configurationmetadata.json They use a simple JSON format with items categorized under either “groups” or “properties” and additional values hints categorized under “hints”, as shown in the following example:
B.1 元数据格式
配置元数据文件位于META-INF / spring-configuration-metadata.json下的jar中。它们使用简单的JSON格式,其中的项目分类为“groups”或“properties”和其他值提示分类在“hints”下。

….

看看它的例子:

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
{"groups": [{
"name": "server",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},
...
],
"properties": [{
"name": "server.port",
"type": "java.lang.Integer",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},{
"name": "server.address",
"type": "java.net.InetAddress",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},
...
],
"hints": [{
"name": "spring.jpa.hibernate.ddl-auto",
"values": [{
"value": "none",
"description": "Disable DDL handling."
},{
"value": "validate",
"description": "Validate the schema, make no changes to the database."
},{
"value": "update",
"description": "Update the schema if necessary."
}]
]}
所以元数据是在位于META-INF/spring-configuration-metadata.json下配置的,下面就列出有关groups、properties、hints使用。

3.1 Group属性

“groups”中包含的JSON对象可以包含下表中显示的属性:
| 名称 | 类型 | 用途 |
|:—-:|:—-:|:—-:|:—-:|:—-|
| name | String | “groups”的全名。这个属性是强制性的 |
| type | String | group数据类型的类名。例如,如果group是基于一个被@ConfigurationProperties注解的类,该属性将包含该类的全限定名。如果基于一个@Bean方法,它将是该方法的返回类型。如果该类型未知,则该属性将被忽略 |
| description | String | 一个简短的group描述,用于展示给用户。如果没有可用描述,该属性将被忽略。推荐使用一个简短的段落描述,第一行提供一个简洁的总结,最后一行以句号结尾 |
| sourceType | String | 贡献该组的来源类名。例如,如果组基于一个被@ConfigurationProperties注解的@Bean方法,该属性将包含@Configuration类的全限定名,该类包含此方法。如果来源类型未知,则该属性将被忽略 |
| sourceMethod | String | 贡献该组的方法的全名(包含括号及参数类型)。例如,被@ConfigurationProperties注解的@Bean方法名。如果源方法未知,该属性将被忽略 |

3.2 Property属性

properties数组中包含的JSON对象可由以下属性构成:
| 名称 | 类型 | 用途 |
|:—-:|:—-:|:—-:|:—-:|:—-|
| name | String | property的全名,格式为小写虚线分割的形式(比如server.servlet-path)。该属性是强制性的 |
| type | String | property数据类型的类名。例如java.lang.String。该属性可以用来指导用户他们可以输入值的类型。为了保持一致,原生类型使用它们的包装类代替,比如boolean变成了java.lang.Boolean。注意,这个类可能是个从一个字符串转换而来的复杂类型。如果类型未知则该属性会被忽略 |
| description | String | 一个简短的组的描述,用于展示给用户。如果没有描述可用则该属性会被忽略。推荐使用一个简短的段落描述,开头提供一个简洁的总结,最后一行以句号结束 |
| sourceType | String | 贡献property的来源类名。例如,如果property来自一个被@ConfigurationProperties注解的类,该属性将包括该类的全限定名。如果来源类型未知则该属性会被忽略 |
| defaultValue | Object | 当property没有定义时使用的默认值。如果property类型是个数组则该属性也可以是个数组。如果默认值未知则该属性会被忽略 |
| deprecated | Deprecated | 指定该property是否过期。如果该字段没有过期或该信息未知则该属性会被忽略 |
| level | String | 弃用级别,可以是警告(默认)或错误。当属性具有警告弃用级别时,它仍然应该在环境中绑定。然而,当它具有错误弃用级别时,该属性不再受管理,也不受约束 |
| reason | String | 对属性被弃用的原因的简短描述。如果没有理由,可以省略。建议描述应是简短的段落,第一行提供简明的摘要。描述中的最后一行应该以句点(.)结束 |
| replacement | String | 替换这个废弃属性的属性的全名。如果该属性没有替换,则可以省略该属性。 |

3.3 hints属性

hints数组中包含的JSON对象可以包含以下属性:
| 名称 | 类型 | 用途 |
|:—-:|:—-:|:—-:|:—-:|:—-|
| name | String | 该提示引用的属性的全名。名称以小写虚构形式(例如server.servlet-path)。果属性是指地图(例如 system.contexts),则提示可以应用于map()或values()的键。此属性是强制性的system.context.keyssystem.context.values |
| values | ValueHint[] | 由ValueHint对象定义的有效值的列表(见下文)。每个条目定义该值并且可以具有描述 |
| providers | ValueProvider[] | 由ValueProvider对象定义的提供者列表(见下文)。每个条目定义提供者的名称及其参数(如果有)。 |

每个”hints”元素的values属性中包含的JSON对象可以包含下表中描述的属性:
| 名称 | 类型 | 用途 |
|:—-:|:—-:|:—-:|:—-:|:—-|
| value | Object | 提示所指的元素的有效值。如果属性的类型是一个数组,那么它也可以是一个值数组。这个属性是强制性的 |
| description | String | 可以显示给用户的值的简短描述。如果没有可用的描述,可以省略。建议描述应是简短的段落,第一行提供简明的摘要。描述中的最后一行应该以句点(.)结束。 |

每个”hints”元素的providers属性中的JSON对象可以包含下表中描述的属性:

| 名称 | 类型 | 用途 |
|:—-:|:—-:|:—-:|:—-:|:—-|
| name | String | 用于为提示所指的元素提供额外内容帮助的提供者的名称。 |
| parameters | JSON object | 提供程序支持的任何其他参数(详细信息请参阅提供程序的文档)。 |

3.4 配置元数据

所以如果想有更好的用户体验,可以为我们自定义的starter配置元数据,下面就为HelloWorld配置元数据。
在META-INF下创建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
{
"hints":[{
"name":"hello.msg",
"values":[{
"value":"你好",
"description":"中文方式打招呼"
},{
"value":"Hi",
"description":"英文方式打招呼"
}]
}],
"groups":[
{
"sourceType": "com.seagetech.spring.boot.helloworld.HelloWorldProperties",
"name": "hello",
"type": "com.seagetech.spring.boot.helloworld.HelloWorldProperties"
}],
"properties":[
{
"sourceType": "com.seagetech.spring.boot.helloworld.HelloWorldProperties",
"name": "hello.msg",
"type": "java.lang.String",
"description": "打招呼的内容",
"defaultValue": "Worlds"
}]
}
然后将autoconfigure模块重新打包,接下来就是在spring-boot-demo项目中使用,如下图所示,就有了属性的提示:

图片

图片

最后更新: 2019年06月06日 17:15

原始链接: https://www.sunnymaple.cn/2017/06/18/三、SpringBoot自定义Starter/

× 请我吃糖~
打赏二维码