前言
項(xiàng)目使用的
springfox-swagger2@2.9.2版本
在 Spring 中集成 swagger 文檔功能,需要通過@ApiModel注解修飾出入?yún)⒌念?,但是如果有兩個(gè)不同包下的相同名稱的類都使用了@ApiModel注解時(shí),會(huì)導(dǎo)致文檔被覆蓋,例如:
- com.example.demo.login.dto.UserDTO
package com.example.demo.login.dto;
@Data
@ApiModel
public class UserDTO{
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("年齡")
private Integer age;
}
- com.example.demo.vip.dto.UserDTO
package com.example.demo.vip.dto;
@Data
@ApiModel
public class UserDTO{
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("會(huì)員級(jí)別")
private Integer vipLevel;
}
上面兩個(gè)類生成出來的文檔會(huì)變成一個(gè)swagger model:

從而導(dǎo)致接口文檔顯示錯(cuò)誤:

解決沖突
修改@ApiModel 注解(推薦)
通過修改@ApiModel 的 value 屬性,來規(guī)避同名沖突,修改之后為:
package com.example.demo.login.dto;
@Data
@ApiModel("login$UserDTO")
public class UserDTO{}
package com.example.demo.vip.dto;
@Data
@ApiModel("vip$UserDTO")
public class UserDTO{}
可以看到生成了兩個(gè)swagger model:

修改類名
把兩個(gè)類名做修改,讓類名不沖突即可。
自定義 swagger 插件
然而上面解決沖突的方式還是太麻煩了,定義一個(gè)文檔的出入?yún)㈩惗眩€要考慮類重名的問題,這種增加心智負(fù)擔(dān)和工作量的問題應(yīng)該要盡量避免掉的,我在想有沒有可能做到每個(gè)類上只需要加上@ApiModel注解就行,剩下的沖突問題全部不用考慮。
于是乎通過跟蹤源碼,找到了swagger model名稱生成的地方,詳見:github

可以看到取名的邏輯是,優(yōu)先取@ApiModel的value值,如果沒有就會(huì)使用defaultTypeName,跟進(jìn)去一看,defaultTypeName是直接取類的簡(jiǎn)稱,代碼如下:

正是因?yàn)槟J(rèn)情況下取類的簡(jiǎn)稱,導(dǎo)致不同包名下的同名類生成出來的swagger model被覆蓋。
原因已經(jīng)分析出來了,接下來其實(shí)就是看看能不能定制化這個(gè)super.nameFor(type)方法了,然而很遺憾這個(gè)方法是寫死的,沒地方下手,但是ApiModelTypeNameProvider這個(gè)類上兩個(gè)注解@Component和@Order已經(jīng)明示了這個(gè)是一個(gè)Spring bean,并且是通過Spring插件機(jī)制進(jìn)行加載的,所以可以自定義一個(gè)插件來完成,在默認(rèn)時(shí)通過完整的類路徑和類名來生成唯一的swagger model,代碼如下:
@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER - 100)
public class FullPathTypeNameProvider extends DefaultTypeNameProvider {
public static final String SPLIT_CHAR = "$";
@Override
public String nameFor(Class<?> type) {
ApiModel annotation = AnnotationUtils.findAnnotation(type, ApiModel.class);
if (annotation == null) {
return super.nameFor(type);
}
if (StringUtils.hasText(annotation.value())) {
return annotation.value();
}
// 如果@ApiModel的value為空,則默認(rèn)取完整類路徑
int packagePathLength = type.getPackage().getName().length();
return Stream.of(type.getPackage().getName().split("\\."))
.map(path -> path.substring(0, 1))
.collect(Collectors.joining(SPLIT_CHAR))
+ SPLIT_CHAR
+ type.getName().substring(packagePathLength + 1);
}
}
效果如下:

后記
通過這一個(gè)小小的優(yōu)化,就可以減少許多團(tuán)隊(duì)中不必要的溝通成本,讓我們能更專注于業(yè)務(wù)開發(fā)。
本文首發(fā)于我的博客:https://monkeywie.cn,歡迎收藏!不定期分享
JAVA、Golang、前端、docker、k8s等干貨知識(shí)。