JAVA中自定义扩展Swagger的能力,自动生成参数取值含义说明,提升开发效率
大家好,又见面了。
在JAVA
做前后端分离的项目开发的时候,服务端需要提供接口文档供周边人员做接口的对接指导。越来越多的项目都在尝试使用一些基于代码自动生成接口文档的工具来替代由开发人员手动编写接口文档,而Swagger作为一款优秀的在线接口文档生成工具,以其功能强大、集成方便而得到了广泛的使用。
在项目中有一种非常常见的场景,就是接口的请求或者响应参数中会有一些字段的取值会限定为固定的几个可选值之一,而在代码中这些可选值往往会通过定义枚举类的方式来承载,比如:
根据操作类型,过滤对应类型的用户操作日志列表
如: http://127.0.0.1:8088/test/queryOperateLogs?operateType=2
这里的请求参数operateType
传入的值需要在后端约定的取值范围内,这个取值范围的定义如下:
@Getter
@AllArgsConstructor
public enum OperateType {
ADD(1, "新增或者创建操作"),
MODIFY(2, "更新已有数据操作"),
DELETE(3, "删除数据操作"),
QUERY(4, "查询数据操作");
private int value;
private String desc;
}
这里就需要我们在接口文档里面将此接口中operateType
的可选值以及每个可选值对应的含义信息都说明清楚,这样调用方在使用的时候才知道应该传入什么值。
我们基于Swagger提供的基础注解能力来实现时,比较常见的会看到如下两种写法:
- 写法1:接口定义的时候,指定入参的取值说明
接口URL中携带的请求入参信息,通过@ApiImplicitParam
注解来告诉调用方此接口允许接收的合法operateType
的取值范围以及各个取值的含义。
比如下面这种场景:
@GetMapping("/queryOperateLogs")
@ApiOperation("查询指定操作类型的操作日志列表")
@ApiImplicitParam(name = "operateType", value = "操作类型,取值说明: 1,新增;2,更新;3,除;4,查询", dataType = "int", paramType = "query")
public List<OperateLog> queryOperateLogs(int operateType) {
return testService.queryOperateLogs(operateType);
}
这样,在swagger界面上就可以显示出字段的取值说明信息。
其实还有一种写法,即在代码的入参前面添加@ApiParam注解的方式来实现。比如:
@GetMapping("/queryOperateLogs")
@ApiOperation("查询指定操作类型的操作日志列表")
public List<OperateLog> queryOperateLogs(@ApiParam(value = "操作类型,取值说明: 1,新增;2,更新;3,删除;4,查询") @RequestParam("type") int operateType) {
return testService.queryOperateLogs(operateType);
}
这样也能达到相同的效果。
- 写法2:请求或者响应的Body体中解释字段的取值说明
对于需要使用json
体进行传输的请求或者响应消息体Model中,可以使用@ApiModelProperty
添加含义说明。
@Data
@ApiModel("操作记录信息")
public class OperateLog {
@ApiModelProperty("操作类型,取值说明: 1,新增;2,更新;3,删除;4,查询")
private int operateType;
@ApiModelProperty("操作用户")
private String user;
@ApiModelProperty("操作详情")
private String detail;
}
同样,在Swagger界面就可以清楚的知道每个字段的具体含义与取值说明。
但是上面的两个写法,都存在着同一个问题,就是如果枚举类中的值内容含义有变更,比如OperateType
枚举类中新增了一个BATCH_DELETE(5, "批量删除")
, 则必须手动去修改所有涉及的接口上的Swagger描述信息。如果有大量场景都涉及此字段,则要改动的地方就非常多,且极易漏掉(因为不好通过代码关联关系直接搜索到)。这样对于开发人员维护起来的成本就会增加,久而久之会导致接口文档的内容与实际代码处理情况不相匹配。
那么,有没有什么简单的方式,可以让接口文档自动根据对应枚举类的内容变更而动态变更呢?
Swagger没有提供原生的此方面能力支持,但是我们可以通过一些简单的方式对Swagger的能力进行扩展,让Swagger支持我们的这种诉求。一起来看下如何实现吧。
扩展可行性分析
既然想要改变生成的Swagger文档中指定字段的描述内容,那么首先就应该是要搞清楚Swagger中现在的内容生成逻辑是如何处理的。我们以@ApiParam
为例进行分析。因为@ApiParam
中指定的内容会被显示到Swagger界面上,那么在Swagger的框架中,一定有个地方会尝试去获取此注解中指定的相关字段值,然后将注解的内容转为界面上的文档内容。所以想要定制,首先必须要了解当前是如何处理的。
翻看Swagger的源码,发现在ApiParamParameterBuilder
类中进行此部分逻辑的处理,处理逻辑如下:
看了下此类是ParameterBuilderPlugin
接口的一个实现类,Swagger框架在遍历并逐个生成parameter
说明信息的时候会被调用此实现类的逻辑来执行。
到这里其实问题就已经很明显了,我们可以自定义一个处理类并实现ParameterBuilderPlugin
接口,然后将我们的诉求在自定义的处理类中进行实现,这样不就可以实现我们的诉求了吗?
相同的策略,我们可以找到处理@ApiImplicitParam
、@ApiModelProperty
对应的接口类。
根据上面的分析,我们只需要提供个自定义实现类,然后分别实现这几个接口就可以搞定我们的诉求了。那应该如何进行封装,将其作为一个通用能力供所有场景使用呢,下面详细讨论下。
自定义注解实现基于枚举类生成描述
前面已经找到了一种思路将我们的定制逻辑注入到Swagger的文档生成框架中进行调用,那么下一步我们就得确认一种相对简单的策略,告诉框架哪个字段需要使用枚举来自动生成取值说明,以及使用哪个枚举类来生成。
这里我们使用自定义注解的方式来实现。Swagger为不同的场景分别提供了@APIParam
、@ApiImplicitParam
、@ApiModelProperty
等不同的注解,我们可以简化下,提供一个统一的自定义注解即可。
比如:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiPropertyReference {
// 接口文档上的显示的字段名称,不设置则使用field本来名称
String name() default "";
// 字段简要描述,可选
String value() default "";
// 标识字段是否必填
boolean required() default false;
// 指定取值对应的枚举类
Class<? extends Enum> referenceClazz();
}
这样呢,对于需要添加取值说明的字段或者接口上,我们就可以添加@ApiPropertyReference
并指定对应的枚举类即可。
比如下面这样:
@Data
@ApiModel("操作记录信息")
public class OperateLog {
@ApiPropertyReference(value = "操作类型", referenceClazz = OperateType.class)
private int operateType;
// ...
}
上面示例代码中,OperateType
是一个已经定义好的枚举类。现在又遇到一个问题,枚举类的实现形式其实也不一样,要如何才能让我们的自动内容生成服务知道获取枚举类中的哪些内容进行处理呢?当然我们可以约定用于Swagger注解中的枚举类必须遵循某个固定的格式,但显然这样实施的难度就会提升,并非是我们想要的结果。
先来看下面给定的这个枚举类,其中包含order
、value
、desc
三个属性值,而value
字段是我们的接口字段需要传入的真实取值,desc
是其对应的含义描述,那么该如何让我们自定义Swagger扩展类知晓应该使用value
和desc
字段来生成文档描述内容呢?
@Getter
@AllArgsConstructor
public enum OperateType {
ADD(1, 11, "新增"),
MODIFY(2, 22, "更新"),
DELETE(3, 33, "删除");
private int order;
private int value;
private String desc;
}
答案其实不陌生,依旧是自定义注解!只要提供个自定义注解,然后添加到枚举类上,指定到底使用枚举类中的哪个字段作为value
值,以及哪个字段用作含义描述desc
字段值就行了。
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {
String value() default "value";
String desc() default "desc";
}
这样,在枚举类上添加下@SwaggerDisplayEnum
并指定下字段的映射,即可用于Swagger注解中:
到这里呢,我们需要的数据来源以及取值转换规则就已经全部确定,剩下的就是如何将一个枚举类中需要的值与描述字段给拼接成想要的内容了。因为是通用能力,所以此处需要通过反射的方式来实现:
private String generateValueDesc(ApiPropertyReference propertyReference) {
Class<? extends Enum> rawPrimaryType = propertyReference.referenceClazz();
SwaggerDisplayEnum swaggerDisplayEnum = AnnotationUtils.findAnnotation(rawPrimaryType,
SwaggerDisplayEnum.class);
String enumFullDesc = Arrays.stream(rawPrimaryType.getEnumConstants())
.filter(Objects::nonNull)
.map(enumConsts -> {
Object fieldValue = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.value());
Object fieldDesc = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.desc());
return fieldValue + ":" + fieldDesc;
}).collect(Collectors.joining(";"));
return propertyReference.value() + "(" + enumFullDesc + ")";
}
测试下输出如下面的格式,自动将枚举类中所有的枚举值及其描述信息都展示出来了。
(1:新增;2:更新;3:删除)
实现自定义扩展处理器
至此呢,我们已经做好了全部的准备工作,下面就可以按照前面分析的策略,来自定义一个实现类去实现相关接口,将我们的处理转换逻辑注入到Swagger框架中去。
@Component
@Primary
public class SwaggerEnumBuilderPlugin implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin {
@Override
public void apply(ModelPropertyContext context) {
// Model中field字段描述的自定义处理策略
}
@Override
public void apply(ParameterContext parameterContext) {
// API中入参的自定义处理策略
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
下面只需要在apply
方法中补充上我们的自定义处理逻辑即可。
自动生成API入参的取值说明
前面已经讲了如何将指定的枚举类中的枚举值生成为描述字符串,在这里我们直接调用,然后将结果设置到context
上下文中即可。
@Override
public void apply(ParameterContext context) {
ApiPropertyReference reference =
context.getOperationContext().findAnnotation(ApiPropertyReference.class).orNull();
String desc = generateValueDesc(reference);
if (StringUtils.isNotEmpty(reference.name())) {
context.parameterBuilder().name(reference.name());
}
context.parameterBuilder().description(desc);
AllowableListValues allowableListValues = getAllowValues(reference);
context.parameterBuilder().allowableValues(allowableListValues);
}
自动生成Model中字段取值说明
同样的策略,我们处理下数据实体类中的field
对应的含义说明。
@Override
public void apply(ModelPropertyContext modelPropertyContext) {
if (!modelPropertyContext.getBeanPropertyDefinition().isPresent()) {
return;
}
BeanPropertyDefinition beanPropertyDefinition = modelPropertyContext.getBeanPropertyDefinition().get();
// 生成需要拼接的取值含义描述内容
String valueDesc = generateValueDesc(beanPropertyDefinition);
modelPropertyContext.getBuilder().description(valueDesc)
.type(modelPropertyContext.getResolver()
.resolve(beanPropertyDefinition.getField().getRawType()));
}
}
效果演示
到这里呢,代码层面的处理就全部完成了。接下来运行下程序,看下效果。先来看下API
接口中入参的含义描述效果:
从界面效果上可以看出,不仅自动将取值说明描述给显示出来,同时界面调测的时候,输入框也变为了下拉框 (因为我们自动给设置了allowableValues
属性),只能输入允许的值。同样的,再来看下Model
中的字段的含义说明描述效果:
可以看到,接口文档中的参数描述信息中,已经自动带上了枚举类中定义的候选取值内容与说明。我们仅修改下枚举类中的内容,其余地方不做修改,再次看下界面,发现Swagger接口中的描述内容已经同步更新为最新的内容。
完美,大功告成。
总结
好啦,关于如何通过自定义注解的方式扩展Swagger的能力让Swagger支持自动从指定的枚举类生成接口文档中的字段描述的实现思路,这里就给大家分享到这里啦。关于本篇内容你有什么自己的想法或独到见解么?欢迎在评论区一起交流探讨下吧。
啰嗦两句
写到这里忽然察觉到,其实 Swagger 会用很容易,但想用好却还是需要一定功夫的,所以趁势决定针对如何在项目中真正的用好Swagger再单独的写一篇文档,近期会分享出来。感兴趣的小伙伴可以关注下,避免迷路。
关于本文中涉及的演示代码的完整示例,我已经整理并提交到github中,如果您有需要,可以自取:https://github.com/veezean/JavaBasicSkills
我是悟道,聊技术、又不仅仅聊技术~
如果觉得有用,请点赞 + 关注让我感受到您的支持。也可以关注下我的公众号【架构悟道】,获取更及时的更新。
期待与你一起探讨,一起成长为更好的自己。
JAVA中自定义扩展Swagger的能力,自动生成参数取值含义说明,提升开发效率的更多相关文章
- 什么是静态代码块?java中如何使用空参构造方法自动生成不同名字的对象,使用非静态的属性和静态属性有什么区别,原因是什么?如何理解static关键字
静态代码块?类加载就执行,最先执行 class demo{ static int num; static{ num=10; num*=3; System.out.println("haha& ...
- Eclipse中设置在创建新类时自动生成注释
方法一:Eclipse中设置在创建新类时自动生成注释 windows-->preference Java-->Code Style-->Code Templates code--&g ...
- springboot成神之——swagger文档自动生成工具
本文讲解如何在spring-boot中使用swagger文档自动生成工具 目录结构 说明 依赖 SwaggerConfig 开启api界面 JSR 303注释信息 Swagger核心注释 User T ...
- java中遍历实体类,获取属性名和属性值
方式一(实体类): //java中遍历实体类,获取属性名和属性值 public static void testReflect(Object model) throws Exception{ for ...
- 【Java思考】Java 中的实参与形参之间的传递到底是值传递还是引用传递呢?
科普: 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数. 引用传递(pass by reference)是指在 ...
- atitit.提升开发效率---使用服务器控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比较
atitit.提升开发效率---使用服务器控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比较 如下列举了服务器控件生命周期所要经历的11个阶段. (1)初始化-- --在此 ...
- Java进阶教程:使用Lombok提升开发效率
Java进阶教程:使用Lombok提升开发效率 Lombok Lombok是一种Java™实用工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO).它通过注释实现这 ...
- atitit.提升开发效率---MDA 软件开发方式的革命(3)----自动化建表
atitit.提升开发效率---MDA 软件开发方式的革命(3)----自动化建表 1. 建模在后自动建表 1 1. 传统上,需要首先建表,在业务编码.. 1 2. 模型驱动建表---更多简化法是在建 ...
- atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較
atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較 例如以下列举了server控件生命周期所要经历的11个阶段. (1)初始 ...
随机推荐
- Maven + SSM环境搭建
Maven + SSM 之前Maven+SSM都是照着搭建的,自己想写点什么的时候发现搭建的过程不清楚. 于是花了时间边整理思路边搭建,并把搭建过程记录下来. 视频看来终觉浅,还是需要自己动手实践,捋 ...
- 【Python基础教程】三种常用、效率最高的Python字符串拼接方法
python字符串连接的方法,一般有以下三种: **方法1:**直接通过加号(+)操作符连接website=& 39;python& 39;+& 39;tab& 39; ...
- Java开发问题:Column 'AAA' in where clause is ambiguous解决办法
当在java开发中遇到了Column 'AAA' in where clause is ambiguous问题时, 你需要去看看:多表查询的时候不同的表是否出现了相同名称相同的列, 如果存在,你需要在 ...
- NC50999 表达式计算4
NC50999 表达式计算4 题目 题目描述 给出一个表达式,其中运算符仅包含+,-,*,/,^(加 减 乘 整除 乘方)要求求出表达式的最终值 数据可能会出现括号情况,还有可能出现多余括号情况 数据 ...
- Codeforces Round #780 (Div. 3)
A. Vasya and Coins 题目链接 题目大意 Vasya 有 a 个 1-burle coin,有 b 个 2-burle coin,问他不能通过不找钱支付的价格的最小值. 思路 如果 a ...
- 【百度飞桨】手写数字识别模型部署Paddle Inference
从完成一个简单的『手写数字识别任务』开始,快速了解飞桨框架 API 的使用方法. 模型开发 『手写数字识别』是深度学习里的 Hello World 任务,用于对 0 ~ 9 的十类数字进行分类,即输入 ...
- 温控器/胎压检测/电表/热泵显示控制器等,低功耗高抗干扰断/段码(字段式)LCD液晶显示驱动IC-VK2C22A/B,替代市面16C22,44*4/40*4点显示
产品品牌:永嘉微电/VINKA 产品型号:VK2C22A/B 封装形式:LQFP52/48 产品年份:新年份 概述: VK2C22是一个点阵式存储映射的LCD驱动器,可支持最大176点(44SEGx4 ...
- 什么是WordPress
首先,假设您没有WordPress的经验: 我将从基础开始. 在本教程中,我将回答问题:"什么是WordPress?" 在这篇文章中,我将说明您可以在哪里获得WordPress以及 ...
- 5.8 NOI 模拟
\(T1\) 比较容易想到的 二分转化为判定,判定是否存在一个子图能保证能一直在\(x\)时间内到达\(n\) 设\(dis(u,v)\)表示\(u->v\)的最短路 先找出候选节点\(i,di ...
- 强大博客搭建全过程(1)-hexo博客搭建保姆级教程
1. 前言 本人本来使用国内的开源项目solo搭建了博客,但感觉1核CPU2G内存的服务器,还是稍微有点重,包括服务器内还搭建了数据库.如果自己开发然后搭建,耗费时间又比较多,于是乎开始寻找轻量型的博 ...