Spring Boot自定义starter必知必会条件

Stella981
• 阅读 991

Spring Boot自定义starter必知必会

前言

在目前的Spring Boot框架中,不管是Spring Boot官方还是非官方,都提供了非常多的starter系列组件,助力开发者在企业应用中的开发,提升研发人员的工作效率,Spring Boot框架提出的约定大于配置的规则,确实帮助开发者简化了以前Spring MVC时代的很多繁杂的配置。让开发者用起来也是非常爽的。

尽管Spring Boot或者一些开源组件已经帮助我们提供了非常多的starter组件,在满足日常的开发中,已经完全没有问题了。但有时候因为需求的可变性,导致企业架构也会随着调整,那么在Spring Boot框架中,官方或开源的第三方starter肯定不能满足企业内部研发人员的要求,这时候就需要开发者自定义企业内部的starter了。

企业或个人自定义Spring Boot的starter组件主要从哪些方面来入手呢,或者什么时候需要自定义starter组件?我个人认为主要有以下几个方面:

  • 规范企业内部编码流程,统一各个技术中间件的代码规范
  • 减少不同类型中间件的使用成本,提升研发人员的研发工作效率
  • 减少冗余代码的使用,统一封装,统一管理。
  • 屏蔽中间件底层细节,暴露配置属性及方法,减少学习使用成本
  • 可能还有更多?

本篇博客结合自身的开发经验以及目前Spring Boot如何配置元数据的官方介绍文档进行结合,进行综合阐述。

Spring Boot官方元数据文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-configuration-metadata.html

封装Spring Boot的starter范围可以是一组规范的业务方法,也可以是通用的中间件底层。开发者通过封装,一定程度上也能起到规范企业编码的作用,同时也能组合复用公共业务逻辑。

那么我们在自定义Spring Boot框架的starter组件时,我们需要准备什么呢?

我认为主要包含以下几个方面:

  • 自定义starter的作用
  • 命名规范
  • 理解Maven或者Gradle依赖包管理的jar包引用传递机制
  • 理解Spring Boot框架中基于Java代码的Configuration配置
  • 理解Spring Boot框架自动装载的过程
  • 学会利用Spring Boot提供的@Conditional系列条件注入充分发挥Spring Boot的优点
  • 学会如何配置自定义starter组件时对外的属性注释配置,可以参考官方文档

自定义starter的作用

我们在自定义starter组件之前,开发者首先需要想清楚,这个starter组件能带来什么,简化开发?或者复用组件的封装供其他同事使用,不写重复代码等等,这些都是需要思考清楚的。

自定义starter的场景很多,例如:

  • 项目中发送短信对接了不同的云服务商,那么可以封装一个短信的starter,屏蔽对接的细节,开发者只需要配置相应的厂商配置信息就可以使用该服务商发送短信了
  • OSS存储对接不同的云服务商,例如阿里云、七牛云、腾讯云等等
  • 企业内部中间件封装使用,简化开发配置
  • more...

根据笔者的经验,我认为自定义的starter的作用无外乎以下几个方面:

  • 充分利用Spring的特性,容器/依赖注入特性,将核心的类组件注入容器中,方便开发者通过注入直接获取拿来使用
  • 通过属性初始化中间件的流程,屏蔽具体的细节
  • ....

starter命名规范

根据Spring Boot的官方要求,如果是开发者指定第三方的starter组件,那么命名规范是yourname-spring-boot-starter

拿Knife4j举例说明如下:

<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <!--在引用时请在maven中央仓库搜索2.X最新版本号--> <version>2.0.8</version></dependency>

而Spring Boot官方维护发布的starter名称规范则是:spring-boot-starter-name

例如我们引用最多的web组件,引用maven配置如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>

jar包引用传递依赖机制

这是自定义封装Spring Boot的starter的前提条件,Gradle笔者并未使用过,这里仅以Maven为例进行阐述说明!

通常我们在封装一个SDK的jar包时,该jar包可能需要引用到第三方的jar包作为依赖包来辅助我们完成对该jar包的封装,但是我们在引用的时候是有讲究的。

针对Spring Boot的自定义starter说到底也是一个jar包,既然是jar包必然会用到第三方的jar(ps:全部都是你写的代码除外),那么我们应该如何明确在starter中的jar包的依赖传递,我认为主要有以下方面:

  • 作为第三方组件使用jar包时,明确第三方组件的版本
  • 作为编译期间的包,需要修改默认的scope范围值,仅仅在编译期间生效,最终打包后引用不传递
  • 自定义封装starter必须引用Spring Boot官方提供的

在定义Spring Boot的第三方starter时,主要用到Maven管理jar包中的两种依赖隔离方式(均可以使用),分别如下:

  • 明确使用<optional>true></optional>属性来强指定jar包不传递
  • 使用<scope>provided</scope>仅仅在编译期间有效,jar包依赖性不传递

一般我们在自定义Spring Boot的starter组件时,都需要引用Spring Boot提供给开发者的依赖包,如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.3.0.RELEASE</version> <scope>provided</scope></dependency>

当然,你也可以使用optional模式,如下:

 <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-autoconfigure</artifactId>  <version>2.3.0.RELEASE</version>  <optional>true</optional></dependency>

Java代码方式的Configuration

基于Java编码的方式配置Spring的Bean已经成了目前的主流,这主要也是得益于Spring Boot框架的流行!

在Spring MVC框架流行的时候,开发人员一般都是通过配置

而通过java编码的方式注入Bean的前提是[@Configuration](https://my.oschina.net/pointdance) 注解加在一个配置Java实体类上即可,示例如下:

@Configurationpublic class MyAutoConfiguration{  //do others... }

Spring Boot框架的自动装载

对于Spring Boot框架自定义的starter组件来说,提供的使用方式而言,我认为目前主要有3种方式,这个主要看封装starter组件的作者如何开放来定

手工[@Import](https://my.oschina.net/u/3201731) 导入

第一种情况:使用者使用[@Import](https://my.oschina.net/u/3201731) 注解将封装的starter组件的Java编码Configuration配置文件进行导入

假设目前封装的一个简单的Configuration配置如下:

@Configurationpublic class DemoAuthConfiguration { @Bean public DemoClient demoClient(){  return new DemoClient(); }}

开发者通过DemoAutoConfiguration.java向Spring的容器中注入了一个DemoClient的实体Bean,由于隶属于不同的package包路径,自定义的starter组件包路径是:com.demo.spring

而开发者的项目主目录包路径是:com.test,所以Spring Boot框架默认是不会加载该配置的,此时,如果开发者要在Spring的容器中获取DemoClient的实体Bean应该怎么办呢?使用者应该在自己的主配置中使用[@Import](https://my.oschina.net/u/3201731) 注解将该配置导入进来交给Spring容器初始化时进行创建,示例如下:

@Import(DemoAutoConfiguration.class)@SpringBootApplicationpublic class DemoDemoApplication {  public static void main(String[] args){  SpringApplication.run(DemoDemoApplication.class, args); }}

提供便于记忆的注解@EnableXXX

@Enablexxx系列注解相信开发者并不陌生,比如我们要使用Spring Boot的定时任务功能,我们会在启动入口引入@EnableScheduling注解,我们使用Springfox的Swagger组件,我们会引入@EnableSwagger2注解

其实这种方式只是为了让开发者能够更加方便的记忆,一个@Enablexxx系列注解,其所代表的功能特点也基本符合该starter组件,是在上面手工通过@Import注解的升级版本。

毕竟Enable单词所代表的含义是启用,这有利于开发者记忆

继续通过上面第一种的示例进行改在,此时,我们可以提供@EnableDemoClient注解,代码示例如下:

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Import(DemoAutoConfiguration.class)public @interface EnableDemoClient {}

大家应该也看到了,我们在该@EnableDemoClient注解中,使用了@Import注解的方式导入了DemoAutoConfiguration配置

此时,我们在项目中可以使用@EnableDemoClient注解了,代码示例如下:

@EnableDemoClient@SpringBootApplicationpublic class DemoDemoApplication {  public static void main(String[] args){  SpringApplication.run(DemoDemoApplication.class, args); }}

当然,@Enable这种注解作用不仅仅局限于此,还可以在该注解上定义外部的配置属性,通过配置该注解的方式达到最终初始化的目的。

自动装载

自动装载是Spring Boot的一重大特点,开发者通过配置文件的方式即可默认加载第三方的starter配置,非常的方便,是上面两种方式的升级版

在之前的基础上,如果开发者希望在Maven的pom.DemoClient类,那么此时我们应该怎么做呢?

我们需要在工程中创建spring.factories文件,文件目录:src/resources/META-INF/spring.factories

spring.factories文件中,配置开发者自定义的configuration类,如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.spring.DemoAutoConfiguration

配置好后,此时再打包我们自定义的starter组件,Spring Boot框架默认会自动装载该配置类,我们在业务代码中也就可以直接使用了

我们可以在SpringApplication.java源码中看到Spring Boot初始化获取该类列表的过程

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {        ClassLoader classLoader = getClassLoader();        // Use names and ensure unique to protect against duplicates        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);        AnnotationAwareOrderComparator.sort(instances);        return instances;}

上述方法中的SpringFactoriesLoader.loadFactoryNames方法如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {        String factoryTypeName = factoryType.getName();        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) {  return result; } try {  //加载META-INF/spring.factories配置,创建MultiValueMap集合放到该集合中  Enumeration<URL> urls = (classLoader != null ?         classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :         ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));  result = new LinkedMultiValueMap<>();  while (urls.hasMoreElements()) {   URL url = urls.nextElement();   UrlResource resource = new UrlResource(url);   Properties properties = PropertiesLoaderUtils.loadProperties(resource);   for (Map.Entry<?, ?> entry : properties.entrySet()) {    String factoryTypeName = ((String) entry.getKey()).trim();    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {     result.add(factoryTypeName, factoryImplementationName.trim());    }   }  }  cache.put(classLoader, result);  return result; } catch (IOException ex) {  throw new IllegalArgumentException("Unable to load factories from location [" +           FACTORIES_RESOURCE_LOCATION + "]", ex); }}

充分利用Spring Boot提供的@Conditional条件注入组件

通过上面的文章介绍,为Spring Boot框架制定一个简单的starter组件相信已经不在话下。但是,这才仅仅开始而已。

在上面介绍的自动装载过程中,开发者是否会存在疑问?

当我们在pom.

那么应该怎么办呢?此时,我们就需要充分利用Spring Boot框架为开发者提供的@Conditional系列条件注入了

条件注入顾名思义,就是只有使用者满足了组件规定的条件时,组件才会向Spring容器中进行注入Bean或者初始化的操作.这种方式也是将选择权直接交给使用者进行选择,减少非必要的组件冲突,是在Spring Boot自定义starter组件中必不可少的一环。

条件注入通常也配合属性类一起来进行使用,提供配置属性选项也是方便使用者在Spring Boot的配置文件application.yml或者application.properties进行配置开启操作,例如我们常见的配置操作如下:

server: port: 18568 servlet: context-path: /test

为Spring Boot的程序指定启动端口号和context-path属性.

我们继续以上面示例中的DemoClient为例进行阐述

假设我们的DemoClient是对接外部API接口的封装组件,该组件规定访问外部API时需要提供appidsecret,根据appid及secret获取token,最后根据token才能调用API获取接口数据,

那么,此时,我们的DemoClient的部分模拟接口代码可能会如下面示例:

public class DemoClien.........
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
8个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这