自定义 spring-boot-starter

基础知识

重要组件

一个完整的 Spring Boot Starter 一般包含以下核心组件:

  • autoconfigure 模块:包含自动配置的代码
  • starter 模块:提供对 autoconfigure 模块的依赖,以及一些其它的依赖

(PS:如果不需要区分这两个概念的话,也可以将自动配置代码模块与依赖管理模块合并成一个模块)

简而言之,其他服务需要启动器的时候,只需要引入一个 starter 就够了

命名规则

Spring Boot 提供的 starter 是以spring-boot-starter-xxx的方式命名。官方建议自定义的 starter 使用xxx-spring-boot-starter命名规则。以区分 Spring Boot生态提供的 starter。

工程架构说明

根据上俩节的描述,用一张图来描述,就是要自己编写:红色的woodwhales-spring-boot-autoconfigure和粉色的woodwhales-spring-boot-starter,将 woodwhales-spring-boot-starter 工程打包成 jar 供其他工程依赖使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@startuml

node "自定义 spring-boot-starter" {
[woodwhales-spring-boot-starter] as starter #Pink
[woodwhales-spring-boot-autoconfigure] as autoconfigure #Tomato
}

node "其他工程" {
[spring-boot-starter-web] as web
[xxx-spring-boot-starter] as myStarter
}

starter -left-> autoconfigure
myStarter .down.> () pom: pom 依赖自定义starter

pom -left-> starter

@enduml

工程结构准备

笔者使用 IDEA 开发工具演示,使用 STS 4 也可以。

开发工具的常用配置参见这俩篇博文:IDEA 常用设置STS 常用设置

在自己喜欢的地方创建一个名称为woodwhles-spring-boot-starter的空工程目录:


在空工程中添加新的工程模块:

woodwhales-spring-boot-starter 工程搭建

创建一个干净的 maven 工程:

指定工程的目录及 maven 坐标信息:

5

创建成功之后空工程中多了一个woodwhles-spring-boot-starter的模块:

woodwhales-spring-boot-autoconfigure 工程搭建

继续在空工程中创建模块,这次使用 Spring Initializr 快速创建 autoconfigure 工程:

指定好 maven 坐标信息及工程名:

只添加spring-boot-configuration-processor依赖:

最后注意检查当前工程是不是被创建在空工程中:

woodwhales-spring-boot-autoconfigure 工程中将无用的文件删除:

再将 woodwhales-spring-boot-autoconfigure 工程中 pom 依赖中的构建工具插件及 spring-boot-starter-test 依赖删除:

最后在 woodwhales-spring-boot-starter 工程中引入 woodwhales-spring-boot-autoconfigure 工程依赖:

上述的工程环境准备好之后,开始编写对应的配置文件或者业务代码。

为了编译不会报警告,可以增加 jdk 版本和字符集约束:

1
2
3
4
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

编写业务配置类 HelloProperties

定义自定义配置类,这个配置类可以读取到引用了本自定义 starter 的工程中的配置信息:

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
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 17:30
* @description:
*/
@ConfigurationProperties("woodwhales.hello")
public class HelloProperties {

private String prefix;
private String suffix;

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getSuffix() {
return suffix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}

编写业务类 HelloService

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
import java.util.Objects;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 17:31
* @description:
*/
public class HelloService {

private HelloProperties helloProperties;

public HelloService() {
}

public HelloService(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}

public String sayHello(String name) {
Objects.requireNonNull(helloProperties, "读取用户配置文件失败");
return String.format("%s - %s - %s", helloProperties.getPrefix(), name, helloProperties.getSuffix());
}

}

编写自动注入配置类 HelloServiceAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 17:37
* @description:
*/
@Configuration
@ConditionalOnWebApplication // 只有是web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloProperties helloProperties;

@Bean
public HelloService helloService() {
return new HelloService(helloProperties);
}
}

@Configuration

@Configuration注解的作用等同于 xml 中的<beans>标签

@EnableConfigurationProperties

@EnableConfigurationProperties注解是用来开启对 3 步骤中@ConfigurationProperties注解配置 Bean 的支持。也就是@EnableConfigurationProperties注解告诉 Spring Boot 能支持@ConfigurationProperties

当然了,也可以在@ConfigurationProperties注解的类上添加@Configuration或者@Component注解。

自动配置类装配生效

要想上述的步骤能生效就要在resources文件目录下创建META-INF文件夹,再创建名称为spring.factories的配置文件,将上述的自动注入配置类配置到 spring boot 中:

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.woodwhales.starter.HelloServiceAutoConfiguration

最终效果如图:

打包与测试

先将woodwhales-spring-boot-autoconfigure工程打包安装到本地 maven 仓库,再打包安装woodwhales-spring-boot-starter,编写简单的web工程测试:

引入依赖:

1
2
3
4
5
<dependency>
<groupId>org.woodwhales.starter</groupId>
<artifactId>woodwhales-spring-boot-starter</artifactId>
<version>1.0.0.0</version>
</dependency>

配置文件配置:

1
2
3
4
5
6
7
server:
port: 8083
address: 0.0.0.0
woodwhales:
hello:
prefix: hi
suffix: welcome to woodwhales's blog

控制层测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.woodwhales.starter.HelloService;

/**
* @projectName: woodwhales-spring-boot-starter-test
* @author: woodwhales
* @date: 20.3.14 18:12
* @description:
*/
@RestController
public class HelloController {
@Autowired
private HelloService helloService;

@GetMapping("/{name}")
public String hello(@PathVariable(name = "name") String name) {
return helloService.sayHello(name);
}
}

postman 测试成功:

应用场景

自定义启动器一般应用场景:动态数据源、登录模块、基于AOP技术实现日志切面等。

日志记录服务

由于日志需要使用到日志打印,所以需要引入一些其他工具,方便开发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional><!-- 防止将spring-boot-starter-web依赖传递到其他模块中 -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional><!-- 防止将lombok依赖传递到其他模块中 -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
<optional>true</optional><!-- 防止将fastjson依赖传递到其他模块中 -->
</dependency>

对上述的启动器进行升级改造:增加自定义的@MyLog注解,实现业务工程在对应的方法上使用这个注解就能打印日志:

编写 @MyLog 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 18:34
* @description:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 方法描述
* @return
*/
String desc() default "";
}
编写 MyLogInterceptor 拦截器

拦截器拦截请求,并获取方法上的注解信息,如果使用了 @MyLog 注解,那么就打印日志:

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
44
45
46
47
48
49
50
51
52
53
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 18:40
* @description:
*/
@Slf4j
public class MyLogInterceptor extends HandlerInterceptorAdapter {

private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
MyLog myLog = method.getAnnotation(MyLog.class);
if (Objects.nonNull(myLog)) {
// 记录方法执行起始时间
startTimeThreadLocal.set(System.currentTimeMillis());
}
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
MyLog myLog = method.getAnnotation(MyLog.class);
if (Objects.nonNull(myLog)) {
// 获取方法执行起始时间
long startTime = startTimeThreadLocal.get();
long expendTime = System.currentTimeMillis() - startTime;

// 打印参数
String requestUri = request.getRequestURI();
String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
String methodDesc = myLog.desc();
String parameters = JSON.toJSONString(request.getParameterMap());
log.info("\n描述:{}\n路径: {}\n方法: {}\n参数:{}\n耗时:{}", methodDesc, requestUri, methodName, parameters, expendTime);
}
}
}
拦截器装配

将拦截器注入 InterceptorRegistry 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @projectName: woodwhales-spring-boot-starter
* @author: woodwhales
* @date: 20.3.14 18:48
* @description:
*/
@Configuration
public class MyLogAutoConfiguration implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyLogInterceptor());
}
}
将自动配置类装配生效

spring.factories配置文件中增加

1
2
3
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.woodwhales.starter.HelloServiceAutoConfiguration,\
org.woodwhales.starter.log.MyLogAutoConfiguration
测试

日志打印:

1
2
3
4
5
2020-03-14 20:18:09.679  INFO 3820 --- [0.0-8083-exec-2] o.w.starter.log.MyLogInterceptor         : 描述:查询用户
2020-03-14 20:18:09.680 INFO 3820 --- [0.0-8083-exec-2] o.w.starter.log.MyLogInterceptor : 路径:/user/list/
2020-03-14 20:18:09.680 INFO 3820 --- [0.0-8083-exec-2] o.w.starter.log.MyLogInterceptor : 方法:org.woodwhales.starter.controller.UserController#list
2020-03-14 20:18:09.680 INFO 3820 --- [0.0-8083-exec-2] o.w.starter.log.MyLogInterceptor : 参数:{}
2020-03-14 20:18:09.680 INFO 3820 --- [0.0-8083-exec-2] o.w.starter.log.MyLogInterceptor : 耗时:2

这个日志打印没有打印请求体数据,如果想看支持打印请求数据的日志的代码,请移步到:

updated updated 2020-03-15 2020-03-15
本文结束感谢阅读

本文标题:自定义 spring-boot-starter

本文作者:木鲸鱼

微信公号:木鲸鱼 | woodwhales

原始链接:https://woodwhales.cn/2020/03/15/063/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%