1.前面的话

我们都知道可以使用 SpringBoot 快速的开发基于 Spring 框架的项目。由于围绕 SpringBoot 存在很多开箱即用的 Starter 依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可。

例如我想要在 SpringBoot 项目中集成 Redis,那么我只需要加入 spring-data-redis-starter 的依赖,并简单配置一下连接信息以及 Jedis 连接池配置就可以。这为我们省去了之前很多的配置操作。甚至有些功能的开启只需要在启动类或配置类上增加一个注解即可完成。

那么如果我们想要自己实现自己的 Starter 需要做些什么呢?下面就开始介绍如何实现自己的 spring-boot-starter-xxx。

2.原理浅谈

从总体上来看,无非就是将Jar包作为项目的依赖引入工程。而现在之所以增加了难度,是因为我们引入的是Spring Boot Starter,所以我们需要去了解Spring Boot对Spring Boot Starter的Jar包是如何加载的?下面我简单说一下。

SpringBoot 在启动时会去依赖的 starter 包中寻找 /META-INF/spring.factories 文件,然后根据文件中配置的路径去扫描项目所依赖的 Jar 包,这类似于 Java 的 SPI 机制。

细节上可以使用@Conditional 系列注解实现更加精确的配置加载Bean的条件。

JavaSPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

3.实现自动配置

接下来我会实现一个普通的Spring Boot Web工程,该工程有一个Service类,类的sayHello方法会返回一个字符串,字符串可以通过application配置文件进行配置。

1.新建一个Spring Boot工程,命名为spring-boot-starter-hello,pom.xml依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-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.项目到这里就差不多了,不过作为依赖,最好还是再做一下收尾工作。

  1. 删除自动生成的启动类SpringBootStarterHelloApplication。
  2. 删除resources下的除META-INF目录之外的所有文件目录。
  3. 删除spring-boot-starter-test依赖并且删除test目录。

7.执行mvn install将spring-boot-starter-hello安装到本地。

当你直接执行时应该会报错,因为我们还需要在pom.xml去掉spring-boot-maven-plugin,也就是下面这段代码。

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

8.随便新建一个Spring Boot工程,引入spring-boot-starter-hello依赖。

<dependency>
      <groupId>com.example</groupId>
      <artifactId>spring-boot-starter-hello</artifactId>
      <version>0.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中文乱码

4.元数据的配置

到目前为止,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.example.springbootstarterhello.HelloProperties",
      "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对象可以包含下表中显示的属性:

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

4.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 替换这个废弃属性的属性的全名。如果该属性没有替换,则可以省略该属性。

4.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 提供程序支持的任何其他参数(详细信息请参阅提供程序的文档)。

5.spring-boot-configuration-processor

配置上述数据是挺麻烦的,如果可以提供一种自动生成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。

6.@Conditional 注解及作用

之前提到了在细节上可以使用@Conditional 系列注解实现更加精确的配置加载Bean的条件。下面列举 SpringBoot 中的所有 @Conditional 注解及作用

注解 作用
@ConditionalOnBean 当容器中有指定的Bean的条件下
@ConditionalOnClass 当类路径下有指定的类的条件下
@ConditionalOnExpression 基于SpEL表达式作为判断条件
@ConditionalOnJava 基于JVM版本作为判断条件
@ConditionalOnJndi 在JNDI存在的条件下查找指定的位
@ConditionalOnMissingBean 当容器中没有指定Bean的情况下
@ConditionalOnMissingClass 当类路径下没有指定的类的条件下
@ConditionalOnNotWebApplication 当前项目不是Web项目的条件下
@ConditionalOnProperty 指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否有指定的资源
@ConditionalOnSingleCandidate 当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean
@ConditionalOnWebApplication 当前项目是Web项目的条件下

比如,注解@ConditionalOnProperty(prefix = "example.service",name= "enabled",havingValue = "true",matchIfMissing = false)的意思是当配置文件中example.service.enabled=true时,条件才成立。

本文鸣谢:

编写自己的SpringBoot-starter

SpringBoot自定义Starter

史上最全的Spring-Boot-Starter开发手册的更多相关文章

  1. 史上最全的Spring Boot配置文件详解

    Spring Boot在工作中是用到的越来越广泛了,简单方便,有了它,效率提高不知道多少倍.Spring Boot配置文件对Spring Boot来说就是入门和基础,经常会用到,所以写下做个总结以便日 ...

  2. 史上最全面的Spring Boot Cache使用与整合

    一:Spring缓存抽象 Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口 ...

  3. 史上最全的Spring Boot Cache使用与整合

    一:Spring缓存抽象# Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接 ...

  4. 【必备】史上最全的浏览器 CSS & JS Hack 手册

    [必备]史上最全的浏览器 CSS & JS Hack 手册   浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技 ...

  5. 最详细的自定义Spring Boot Starter开发教程

    1. 前言 随着Spring的日渐臃肿,为了简化配置.开箱即用.快速集成,Spring Boot 横空出世. 目前已经成为 Java 目前最火热的框架了.平常我们用Spring Boot开发web应用 ...

  6. Spring Boot Starter 开发指南

    Spring Boot Starter是什么? 依赖管理是任何复杂项目的关键部分.以手动的方式来实现依赖管理不太现实,你得花更多时间,同时你在项目的其他重要方面能付出的时间就会变得越少. Spring ...

  7. spring boot starter开发

    作为公司的技术保障部,一直承担着技术方向的把控,最近公司准备全面转入spring boot的开发.所以我们部门也一直在调研相关的技术知识点: 使用springboot开发应用已经有一段时间了,我们都沉 ...

  8. 【必备】史上最全的浏览器 CSS & JS Hack 手册(转)

    浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技能.如果有一份浏览器 Hack 手册,那查询起来就方便多了.这篇文章就向 ...

  9. 史上最全的浏览器 CSS & JS Hack 手册

    浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技能.如果有一份浏览器 Hack 手册,那查询起来就方便多了.这篇文章就向 ...

  10. 【收藏】史上最全的浏览器 CSS & JS Hack 手册

    浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技能.如果有一份浏览器 Hack 手册,那查询起来就方便多了.这篇文章就向 ...

随机推荐

  1. Java地位被撼动?Java与JavaScript的趣事连载

    第一回 JavaScript的进攻 公元2014年,Java 第八代国王终于登上了王位. 第一次早朝,国王坐在高高的宝座上,看着毕恭毕敬的大臣,第一次体会到了皇权的威力. 德高望重的IO大臣颤悠悠地走 ...

  2. Python面向对象 组合(选课系统示例)

    # Author : Kelvin # Date : 2019/1/15 20:44 """ 学校与老师关联 课程与老师和学校关联 """ ...

  3. 在Linux系统配置Nodejs环境的最简单步骤,部署多个thinkjs(nodejs)项目

    发现一台服务器部署管理多个nodejs服务,可以采用二级域名weekly.mwcxs.top,也可以采用固定后缀www.mwcxs.top/weekly的方式,本文先从固定后缀的方式部署管理多个nod ...

  4. Mybatis之旅第三篇-SqlMapConfig.xml全局配置文件解析

    一.前言 刚换工作,为了更快的学习框架和了解业务,基本每天都会加班,导致隔了几天没有进行总结,心里总觉得不安,工作年限越长越感到学习的重要性,坚持下去!!! 经过前两篇的总结,已经基本掌握了mybat ...

  5. 【Android Studio安装部署系列】二十八、Android Studio查看其它APP的布局结构

    概述 日常使用别家的APP过程中,会遇到一些比较好看的布局,这时候我们就想学习一下别人的布局结构,以便参考. (1)手机连接电脑.设置手机为USB调试模式 参考<[Android Studio安 ...

  6. SmartSql 介绍

    介绍 SmartSql = MyBatis + Cache(Memory | Redis) + R/W Splitting +Dynamic Repository + Diagnostics .... ...

  7. 使用Atlas进行元数据管理之容错和高可用

    1. 介绍 Apache Atlas使用各种系统并与之交互,为数据管理员提供元数据管理和数据血缘信息.通过适当地选择和配置这些依赖关系,可以使用Atlas实现高度的服务可用性.本文档介绍了Atlas中 ...

  8. K2制作流程

    K2流程制作注意事项 1:分析需求 2:实施 步骤1:绘制流程图 步骤2:添加datafield[必备:ActJumped  IsPass] 步骤3:添加线规则(如下图所示,在添加完毕规则之后,再给同 ...

  9. MySQL 笔记整理(19) --为什么我只查一行的语句,也执行这么慢?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 19) --为什么我只查一行的语句,也执行这么慢? 需要说明一下,如果M ...

  10. 了解一下 - Base64

    Base64编码是最常见的编码方式(使用64个字符表示任意8bit字节序列),是一种基于64个可打印字符来表示任意二进制数据的方法,是从二进制转换到可见字符的过程. 使用场景 数据加密后通过Base6 ...