# swagger优雅显示全部状态码
背景: 后端在与前端交流的时候,常常需要用到swagger文档去沟通接口。虽然swagger中有对于枚举类和返回状态码的支持,但是需要在代码中写好返回响应,会带来很多的多余代码。对实际开发并不友好。
引入的依赖
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
| <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.21</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>1.5.21</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency>
|
swagger本身对于状态码的支持
1 2 3 4
| @ApiResponses({ @ApiResponse(code = 200, message = "成功",response = CustomEnumConstant.class), @ApiResponse(code = 500, message = "失败") })
|
实际效果如下:

ps: 对于此,其实我是真的不想写这么多响应在代码里面,如果真的要写,不如直接去Apifox里面,测一遍接口保存测试用例和响应示例。
在网上找的对于枚举类的支持方法
基本就是改写ModelPropertyBuilderPlugin或者ExpandedParameterBuilderPlugin。
- ==ModelPropertyBuilderPlugin==是控制模型属性的构建
- ==ExpandedParameterBuilderPlugin==是控制参数基本属性的构建,如参数的类型、描述、是否必填等。实现这个接口可以控制参数的基本展示形式
但是都满足不了我的需求,我想==把整一个状态码枚举类给前端看==。因为单独的在一个模型属性中是显示不了全部后台定义的状态码的。
解决办法
我就想到能不能通过状态码枚举类,动态生成一个类,交给swagger扫描到文档中,这样就可以达到很好的显示效果了.
这个的难点有两个:
- 1.如何动态生成类
- 2.如何交给swagger扫描
解决过程
对于如何动态生成类,我借鉴了GPT的说法.可以使用cglib的api或者java原生的javassist.
对于cglib,api有些学习难度.我借鉴了一篇博客
https://developer.aliyun.com/article/1388546
最终用的javassist.
如何交给swagger扫描我也是在上面的博客中找到的办法.
下面直接贴上显示状态码的代码
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| import com.fasterxml.classmate.TypeResolver; import io.swagger.annotations.ApiModelProperty; import javassist.*; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.StringMemberValue; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterContext; import javassist.bytecode.annotation.Annotation;
import java.lang.reflect.Modifier;
@Configuration @Order(-19999) @Slf4j public class SwaggerModelReader implements ParameterBuilderPlugin {
@Value("${httpCode}") private String httpCode;
private Integer cnt = 0;
@Value("${name}") private String name;
@Autowired private TypeResolver typeResolver;
@Override public void apply(ParameterContext context) {
if (cnt == 1) { Class<?> enumClass; try { enumClass = Class.forName(httpCode); } catch (ClassNotFoundException e) { log.error("找不到枚举类"); throw new RuntimeException(e); } Object[] enumConstants = enumClass.getEnumConstants();
Class newClass = createRefModelIgp(enumConstants); context.getDocumentationContext() .getAdditionalModels() .add(typeResolver.resolve(newClass)); context.parameterBuilder() .parameterType("body") .modelRef(new ModelRef(name)) .name(name); }
cnt++; }
private Class createRefModelIgp(Object[] enumConstants) { try {
ClassPool pool = ClassPool.getDefault(); int lastDotIndex = httpCode.lastIndexOf("."); String prefix = httpCode.substring(0, lastDotIndex);
CtClass ctClass = pool.makeClass(prefix + "." + name); createCtFileds(enumConstants, ctClass); return ctClass.toClass(); } catch (Exception e) { e.printStackTrace(); return null; } }
@Override public boolean supports(DocumentationType delimiter) { return true; }
public void createCtFileds(Object[] enumConstants, CtClass ctClass) { for (Object object : enumConstants) { try { String fieldName = ((Enum<?>) object).name(); HttpCode httpCode = (HttpCode) object; int code = httpCode.getCode(); String message = httpCode.getMessage(); String fieldsInfo = code + "\t" + message;
CtField ctField = new CtField(ClassPool.getDefault().get("java.lang.String"), fieldName, ctClass); ctField.setModifiers(Modifier.PUBLIC);
String apiModelPropertyValue = fieldsInfo; if (StringUtils.isNotBlank(apiModelPropertyValue)) { ConstPool constPool = ctClass.getClassFile().getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool); ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue, constPool)); attr.addAnnotation(ann); ctField.getFieldInfo().addAttribute(attr); }
ctClass.addField(ctField); } catch (Exception e) { e.printStackTrace(); } } } }
|
上面的是解析的方法,状态码想要显示,需要实现下面这个接口
1 2 3 4
| public interface HttpCode { int getCode(); String getMessage(); }
|
然后再yml配置中添加以下
1 2 3 4
| httpCode: com.xxxx.xxxx.xxxx.HttpStatus
name: 状态码
|
显示效果如下:

遇到的问题
- 1.如何通过反射拿到枚举的常量
- 2.动态生成类的CtClass ctClass = pool.makeClass(prefix + “.” + name);报错frozen class
解决
- 1.写一个接口让用户实现就可以保证反射取得枚举常量通用性
- 2.开头的cnt就是解决报错frozen class的,具体原因未知