简史
让我们先理一下springfox与swagger的关系。
swagger是一个流行的API开发框架,这个框架以“开放API声明”(OpenAPI Specification,OAS)为基础,对整个API的开发周期都提供了相应的解决方案,是一个非常庞大的项目(包括设计、编码和测试,几乎支持所有语言)。
OAS本身是一个API规范,它用于描述一整套API接口,包括一个接口是GET还是POST请求啊,有哪些参数哪些header啊,都会被包括在这个文件中。它在设计的时候通常是YAML格式,这种格式书写起来比较方便,而在网络中传输时又会以json形式居多,因为json的通用性比较强。
由于Spring的流行,Marty Pitt编写了一个基于Spring的组件swagger-springmvc,用于将swagger集成到springmvc中来。而springfox则是从这个组件发展而来,同时springfox也是一个新的项目,本文仍然是使用其中的一个组件springfox-swagger2。
pringfox-swagger2依然是依赖OSA规范文档,也就是一个描述API的json文件,而这个组件的功能就是帮助我们自动生成这个json文件,我们会用到的另外一个组件springfox-swagger-ui就是将这个json文件解析出来,用一种更友好的方式呈现出来。
SpringFox
Automated JSON API documentation for API's built with Spring.
Getting Started
For new projects
For Maven
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-boot-starter</artifactId>
- <version>3.0.0</version>
- </dependency>
For Gradle
implementation "io.springfox:springfox-boot-starter:<version>"
Swagger的可扩展组件
在源码中( https://github.com/springfox/springfox ), 可以看到下图所示的一些Plugin结尾的接口文件,我们就是要在这些上面做文章的。
自定义扩展功能的话,只需要实现某个xxxPlugin的接口中的apply方法就可以。apply方法中我们去手动扫描我们自定义的注解,然后加上相关实现的逻辑即可。
代码示例:
- /**
- * 针对传值的参数自定义注解
- * @author zhenghui
- * @date 2020年9月13日13:25:18
- * @desc 读取自定义的属性并动态生成model
- */
- @Configuration
- @Order(-19999) //plugin加载顺序,默认是最后加载
- public class SwaggerModelReader implements ParameterBuilderPlugin {
-
- @Autowired
- private TypeResolver typeResolver;
-
- static final Map<String,String> MAPS = new HashMap<>();
- static {
- MAPS.put("byte","java.lang.Byte");
- MAPS.put("short","java.lang.Short");
- MAPS.put("integer","java.lang.Integer");
- MAPS.put("long","java.lang.Long");
- MAPS.put("float","java.lang.Float");
- MAPS.put("double","java.lang.Double");
- MAPS.put("char","java.lang.Character");
- MAPS.put("string","java.lang.String");
- MAPS.put("boolean","java.lang.Boolean");
- }
-
- //根据用户自定义的类型拿到该类型所在的包的class位置
- static public String getTypePath(String key){
- return key==null || !MAPS.containsKey(key.toLowerCase()) ? null : MAPS.get(key.toLowerCase());
- }
-
-
- @Override
- public void apply(ParameterContext context) {
- ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();
-
- //自定义的注解
- Optional<ApiIgp> apiIgp = methodParameter.findAnnotation(ApiIgp.class);
- Optional<Apicp> apicp = methodParameter.findAnnotation(Apicp.class);
-
-
-
- if (apiIgp.isPresent() || apicp.isPresent()) {
- Class originClass = null;
- String[] properties = null; //注解传递的参数
- Integer annoType = 0;//注解的类型
- String name = null + "Model" + 1; //model 名称 //参数名称
-
- String[] noValues = null;
- String[] noValueTypes = null;
- String[] noVlaueExplains = null;
- //拿到自定义注解传递的参数
- if (apiIgp.isPresent()){
- properties = apiIgp.get().values(); //排除的
- originClass = apiIgp.get().classPath();//原始对象的class
- name = apiIgp.get().modelName() ; //model 名称 //参数名称
-
- noValues = apiIgp.get().noValues();
- noValueTypes = apiIgp.get().noValueTypes();
- noVlaueExplains = apiIgp.get().noVlaueExplains();
-
- }else {
- properties = apicp.get().values(); //需要的
- annoType = 1;
- originClass = apicp.get().classPath();//原始对象的class
- name = apicp.get().modelName() ;//自定义类的名字
- noValues = apicp.get().noValues();
- noValueTypes = apicp.get().noValueTypes();
- noVlaueExplains = apicp.get().noVlaueExplains();
- }
-
- //生成一个新的类
- Class newClass = createRefModelIgp(properties, noValues, noValueTypes, noVlaueExplains, name, originClass, annoType);
-
-
- context.getDocumentationContext()
- .getAdditionalModels()
- .add(typeResolver.resolve(newClass)); //向documentContext的Models中添加我们新生成的Class
-
-
- context.parameterBuilder() //修改model参数的ModelRef为我们动态生成的class
- .parameterType("body")
- .modelRef(new ModelRef(name))
- .name(name);
-
- }
-
-
- }
-
- /**
- *
- * @param properties annoType=1:需要的 annoType=0:排除的
- * @param noValues
- * @param noValueTypes
- * @param noVlaueExplains
- * @param name 创建的mode的名称
- * @param origin
- * @param annoType 注解的类型
- * @return
- */
- private Class createRefModelIgp(String[] properties, String[] noValues, String[] noValueTypes, String[] noVlaueExplains, String name, Class origin, Integer annoType) {
- try {
- //获取原始实体类中所有的变量
- Field[] fields = origin.getDeclaredFields();
- //转换成List集合,方便使用stream流过滤
- List<Field> fieldList = Arrays.asList(fields);
- //把传入的参数也转换成List
- List<String> dealProperties = Arrays.asList(properties);//去掉空格并用逗号分割
- //过滤出来已经存在的
- List<Field> dealFileds = fieldList
- .stream()
- .filter(s ->
- annoType==0 ? (!(dealProperties.contains(s.getName()))) //如果注解的类型是0,说明要取反
- : dealProperties.contains(s.getName())
- ).collect(Collectors.toList());
-
- //存储不存在的变量
- List<String> noDealFileds = Arrays.asList(noValues);
- List<String> noDealFiledTypes = Arrays.asList(noValueTypes);
- List<String> noDealFiledExplains = Arrays.asList(noVlaueExplains);
-
-
- //创建一个类
- ClassPool pool = ClassPool.getDefault();
- CtClass ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);
-
- //创建对象,并把已有的变量添加进去
- createCtFileds(dealFileds,noDealFileds,noDealFiledTypes,noDealFiledExplains,ctClass,annoType);
-
- //返回最终的class
- return ctClass.toClass();
-
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
-
- @Override
- public boolean supports(DocumentationType delimiter) {
- return true;
- }
-
- /**
- * 根据propertys中的值动态生成含有Swagger注解的javaBeen
- *
- * @param dealFileds 原始对象中已经存在的对象属性名字
- * @param noDealFileds 原始对象中不存在的对象属性名字
- * @param noDealFiledTypes 原始对象中不存在的对象属性的类型,八大基本类型例如:dounle等,还有String
- * @param noDealFiledExplains 自定义变量的参数说明
- * @param ctClass 源class
- * @throws CannotCompileException
- * @throws NotFoundException
- * @throws ClassNotFoundException
- */
- public void createCtFileds(List<Field> dealFileds, List<String> noDealFileds, List<String> noDealFiledTypes,List<String> noDealFiledExplains, CtClass ctClass, Integer annoType) {
- //添加原实体类存在的的变量
- // if(annoType==1)
- for (Field field : dealFileds) {
- CtField ctField = null;
- try {
- ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
- } catch (CannotCompileException e) {
- System.out.println("找不到了1:"+e.getMessage());
- } catch (NotFoundException e) {
- System.out.println("找不到了2:"+e.getMessage());
- }
- ctField.setModifiers(Modifier.PUBLIC);
- ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
- String apiModelPropertyValue = java.util.Optional.ofNullable(annotation).map(s -> s.value()).orElse("");
-
-
-
- if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
- 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);
- }
- try {
- ctClass.addField(ctField);
- } catch (CannotCompileException e) {
- System.out.println("无法添加字段1:"+e.getMessage());
- }
- }
-
- //添加原实体类中不存在的的变量
- for (int i = 0; i < noDealFileds.size(); i++) {
- String valueName = noDealFileds.get(i);//变量名字
- String valueType = noDealFiledTypes.get(i);//变量的类型
- valueType=getTypePath(valueType);
-
- //根据变量的类型,变量的名字,变量将要在的类 创建一个变量
- CtField ctField = null;
- try {
- ctField = new CtField(ClassPool.getDefault().get(valueType), valueName, ctClass);
- } catch (CannotCompileException e) {
- System.out.println("找不到了3:"+e.getMessage());
- } catch (NotFoundException e) {
- System.out.println("找不到了4:"+e.getMessage());
- }
- ctField.setModifiers(Modifier.PUBLIC);//设置权限范围是私有的,或者public等
-
- if(noDealFiledExplains.size()!=0){
- //参数设置描述
- String apiModelPropertyValue = (apiModelPropertyValue=noDealFiledExplains.get(i))==null?"无描述":apiModelPropertyValue;//参数描述
-
- System.out.println(apiModelPropertyValue);
-
- if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
- 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);
- }
-
- }
-
- //把此变量添加到类中
- try {
- ctClass.addField(ctField);
- } catch (CannotCompileException e) {
- System.out.println("无法添加字段2:"+e.getMessage());
- }
-
- }
-
- }
- }
Swagger 常用注解
@Api
用在类上,说明该类的作用
@Api(value = "UserController", description = "用户相关api")
@ApiOperation
用在方法上,说明方法的作用
@ApiOperation(value = "查找用户", notes = "查找用户", httpMethod = "GET", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ApiImplicitParams
用在方法上包含一组参数说明
@ApiImplicitParam
用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
paramType:参数放在哪个地方
- header–>请求参数的获取:@RequestHeader
- query–>请求参数的获取:@RequestParam
- path(用于restful接口)–>请求参数的获取:@PathVariable
- body(不常用)
- form(不常用)
-
- name:参数名
- dataType:参数类型
- required:参数是否必须传
- value:参数的意思
- defaultValue:参数的默认值
- @ApiImplicitParams({
- @ApiImplicitParam(name = "id", value = "唯一id", required = true, dataType = "Long", paramType = "path"),
- })
@ApiResponses
用于表示一组响应
@ApiResponse
用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如”请求参数没填好”
response:抛出异常的类
- @ApiResponses(value = {
- @ApiResponse(code = 400, message = "No Name Provided")
- })
@ApiModel
Swagger-core builds the model definitions based on the references to them throughout the API introspection.
The @ApiModel allows you to manipulate the meta data of a model from a simple description or name change to a definition of polymorphism.
描述一个Model的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModel(value = "用户实体类")
@ApiModelProperty
描述一个model的属性
- @ApiModelProperty(value = "登录用户")
- @ApiIgnore //使用这个注解忽略这个接口
参考资料
https://blog.csdn.net/qq_17623363/article/details/109259315https://blog.csdn.net/wsh900221/article/details/80508548