自定義SpringBoot+Swagger中@ApiModel默認(rèn)名稱

前言

項(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)先取@ApiModelvalue值,如果沒有就會(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,歡迎收藏!不定期分享JAVAGolang、前端、docker、k8s等干貨知識(shí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容