我们都知道可以使用 SpringBoot 快速的开发基于 Spring 框架的项目。由于围绕 SpringBoot 存在很多开箱即用的 Starter 依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可。
例如我想要在 SpringBoot 项目中集成 Redis,那么我只需要加入 spring-data-redis-starter 的依赖,并简单配置一下连接信息以及 Jedis 连接池配置就可以。这为我们省去了之前很多的配置操作。甚至有些功能的开启只需要在启动类或配置类上增加一个注解即可完成。
那么如果我们想要自己实现自己的 Starter 需要做些什么呢?下面就开始介绍如何实现自己的 spring-boot-starter-xxx。
从总体上来看,无非就是将Jar包作为项目的依赖引入工程。而现在之所以增加了难度,是因为我们引入的是Spring Boot Starter,所以我们需要去了解Spring Boot对Spring Boot Starter的Jar包是如何加载的?下面我简单说一下。
SpringBoot 在启动时会去依赖的 starter 包中寻找 /META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包,这类似于 Java 的 SPI 机制。
细节上可以使用@Conditional 系列注解实现更加精确的配置加载Bean的条件。
JavaSPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
接下来我会实现一个普通的Spring Boot Web工程,该工程有一个Service类,类的sayHello方法会返回一个字符串,字符串可以通过application配置文件进行配置。
1.新建一个Spring Boot工程,命名为spring-boot-starter-hello,pom.xml依赖:
dependency
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-starter-web/artifactId
/dependency
2.新建HelloProperties类,定义一个hello.msg参数(默认值World!)。
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
/**
* 打招呼的内容,默认为“World!”
*/
private String msg = "World!";
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3.新建HelloService类,使用HelloProperties类的属性。
@Service
public class HelloService {
@Autowired
private HelloProperties helloProperties;
/**
* 打招呼方法
*
* @param name 人名,向谁打招呼使用
* @return
*/
public String sayHello(String name) {
return "Hello " + name + " " + helloProperties.getMsg();
}
}
4.自动配置类,可以理解为实现自动配置功能的一个入口。
//定义为配置类
@Configuration
//在web工程条件下成立
@ConditionalOnWebApplication
//启用HelloProperties配置功能,并加入到IOC容器中
@EnableConfigurationProperties({HelloProperties.class})
//导入HelloService组件
@Import(HelloService.class)
//@ComponentScan
public class HelloAutoConfiguration {
}
5.在resources目录下新建META-INF目录,并在META-INF下新建spring.factories文件,写入:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.springbootstarterhello.HelloAutoConfiguration
6.项目到这里就差不多了,不过作为依赖,最好还是再做一下收尾工作。
7.执行mvn install将spring-boot-starter-hello安装到本地。
当你直接执行时应该会报错,因为我们还需要在pom.xml去掉spring-boot-maven-plugin,也就是下面这段代码。
build
plugins
plugin
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-maven-plugin/artifactId
/plugin
/plugins
/build
8.随便新建一个Spring Boot工程,引入spring-boot-starter-hello依赖。
dependency
groupIdcom.example/groupId
artifactIdspring-boot-starter-hello/artifactId
version0.0.1-SNAPSHOT/version
/dependency
9.在新工程中使用spring-boot-starter-hello的sayHello功能。
@SpringBootApplication
@Controller
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Autowired
private HelloService helloService;
@RequestMapping(value = "/sayHello")
@ResponseBody
public String sayHello(String name){
System.out.println(helloService.sayHello(name));
return helloService.sayHello(name);
}
}
访问http://localhost:8080/sayHello?name=Mark
浏览器打印:Hello Mark World!
在application.properties文件中配置属性:hello.msg = 你好!
重启项目,再次刷新访问,浏览器响应:Hello Mark 你好!
如果你遇到中文乱码,可以参考我的博客 Spring boot读取application.properties中文乱码
到目前为止,spring-boot-starter-hello的自动配置功能已实现,并且正确使用了,但还有一点不够完美,如果你也按上面步骤实现了自己的spring-boot-starter-hello自动配置,在application.properties中配置hello.msg属性时,你会发现并没有提示你有关该配置的信息,但是如果你想配置tomcat端口时,输入server.port是有提示的:
这种功能如何做呢?在Spring Boot官方文档中就已经给出了方法,新建META-INF/spring-configuration-metadata.json文件,进行配置。
那如何对spring-boot-starter-hello项目配置元数据呢?代码如下:
{
"hints":[{
"name":"hello.msg",
"values":[{
"value":"你好",
"description":"中文方式打招呼"
},{
"value":"Hi",
"description":"英文方式打招呼"
}]
}],
"groups":[
{
"sourceType": "com.seagetech.spring.boot.helloworld.HelloWorldProperties",
"name": "hello",
"type": "com.example.springbootstarterhello.HelloProperties"
}],
"properties":[
{
"sourceType": "com.example.springbootstarterhello.HelloProperties",
"name": "hello.msg",
"type": "java.lang.String",
"description": "打招呼的内容",
"defaultValue": "Worlds"
}]
}
然后我们将spring-boot-starter-hello项目重新打包使用,如下图所示,就有了属性的提示:
下面我们就列出有关groups、properties、hints具体使用,不过我建议你可以先跳过这部分枯燥的内容。
4.1 Group属性
“groups”中包含的JSON对象可以包含下表中显示的属性:
4.2 Property属性
properties数组中包含的JSON对象可由以下属性构成:
4.3 hints属性
hints数组中包含的JSON对象可以包含以下属性:
每个”hints”元素的values属性中包含的JSON对象可以包含下表中描述的属性:
每个”hints”元素的providers属性中的JSON对象可以包含下表中描述的属性:
配置上述数据是挺麻烦的,如果可以提供一种自动生成spring-configuration-metadata.json的依赖就好了。别说,还真有。spring-boot-configuration-processor依赖就可以做到,它的基本原理是在编译期使用注解处理器自动生成spring-configuration-metadata.json文件。文件中的数据来源于你是如何在类中定义hello.msg这个属性的,它会自动采集hello.msg的默认值和注释信息。不过我在测试时发现了中文乱码问题,而且网上有关spring-boot-configuration-processor的学习文档略少。
下面我贴出使用spring-boot-configuration-processor自动生成的spring-configuration-metadata.json文件内容:
{
"groups": [
{
"name": "hello",
"type": "com.example.springbootstarterhello.HelloProperties",
"sourceType": "com.example.springbootstarterhello.HelloProperties"
}
],
"properties": [
{
"name": "hello.msg",
"type": "java.lang.String",
"description": "打招呼的内容,默认为“World!”",
"sourceType": "com.example.springbootstarterhello.HelloProperties",
"defaultValue": "World!"
}
],
"hints": []
}
可以看到properties里的description属性值来源于注释信息,defaultValue值来源于代码中书写的默认值。
这一步需要在idea设置中搜索Annotation Processors,勾住Enable annonation processing。
之前提到了在细节上可以使用@Conditional 系列注解实现更加精确的配置加载Bean的条件。下面列举 SpringBoot 中的所有 @Conditional 注解及作用
比如,注解
@ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true")
的意思是当配置文件中
example.service.enabled=true
时,条件才成立。
本文鸣谢:********
SpringBoot自定义Starter
原文始发于微信公众号(程序员柯南):