拓展Swagger功能,支持JsonView视图

本文档介绍了一种方法,通过扩展Swagger功能,使其能够识别并正确渲染Jackson的@JsonView注解。作者在找不到解决方案的情况下,自行开发了一个解决方案,利用javassist库动态生成新类来处理包含@JsonView的字段,确保在Swagger接口文档中只显示相应的字段。代码示例展示了如何在实体类和接口方法中使用@JsonView,并提供了处理请求参数和返回值的代码片段。
摘要由CSDN通过智能技术生成

拓展Swagger功能,支持JsonView视图,全网首发


对象使用jackson进行json序列化和反序列化时,能通过@JsonView设置要屏蔽的字段,但swagger不能识别@JsonView,在接口文档中,应该被屏蔽的字段仍会展示出来,此代码作用就是让swagger能识别JsonView注解,正确渲染字段。

功能一年前就写好了,当时找遍全网也没有解决方案,只好自己开发。
过年在家太无聊,写成博客分享出来。

下面贴出代码片举例说明

也可以从仓库拉取代码,测试效果
gitee上的Demo工程

实体类代码
包含username、birthday、guardian三个属性
其中username和guardian属于UserSimpleView视图,birthday属于UserDetailView视图

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import java.util.List;

/**
 * @ApiModel 注解中必须用description,不能用value
 * 否则无法识别
 *
 * 支持JsonView的继承关系
 * 支持泛型类、泛型字段、继承的字段、嵌套对象
 */
@ApiModel(description = "用户对象")
public class User {

    public interface UserSimpleView {
    }

    public interface UserDetailView extends UserSimpleView {
    }

    @ApiModelProperty(value = "姓名", required = true)
    @JsonView(UserSimpleView.class)
    private String username;

    @ApiModelProperty(value = "生日", required = true)
    @JsonView(UserDetailView.class)
    private String birthday;

    @ApiModelProperty(value = "监护人", required = true)
    @JsonView(UserSimpleView.class)
    private List<User> guardian;

	// getter、setter...
}

接口代码
接口参数的User使用@JsonView(User.UserDetailView.class)修饰,三个字段都参与json反序列化;
接口返回值的User使用@JsonView(User.UserSimpleView.class)修饰,json序列化时birthday字段会被屏蔽;

    /**
     * 使用@RequestMapping时,一定要设置method参数
     *
     * @param user
     * @return
     */
    @ApiOperation("注册人员信息")
    @RequestMapping(value = "/addUser", method = RequestMethod.POST)
    @JsonView(User.UserSimpleView.class)
    public User addUser(@RequestBody @JsonView(User.UserDetailView.class) User user) {
        return user;
    }

请求参数示例

{
  "username": "小鸟",
  "birthday": "2022-01-29",
  "guardian": [
    {
      "username": "鸟爸爸",
      "birthday": "2021-06-18",
      "guardian": null
    },
    {
      "username": "鸟妈妈",
      "birthday": "2021-11-11",
      "guardian": null
    }
  ]
}

返回值示例
返回的json对象中,birthday字段消失了

{
  "username": "小鸟",
  "guardian": [
    {
      "username": "鸟爸爸",
      "guardian": null
    },
    {
      "username": "鸟妈妈",
      "guardian": null
    }
  ]
}

swagger文档
返回值的字段仍然包含birthday字段
(右边是swagger-bootstrap-ui的界面)
请添加图片描述

增强后的swagger文档
返回值的birthday字段消失了
(右边是swagger-bootstrap-ui的界面)
请添加图片描述

原理
在swagger扫描接口时,用javassist复制原本的参数和返回值的Class生成新的Class,跳过要忽略的字段,然后把新的Class注册到swagger中。

注意事项
1、接口使用@RequestMapping时,一定要设置method参数
2、@ApiModel 中必须用description,用value会导致无法识别
3、能识别泛型类、泛型字段、继承的字段、嵌套对象
4、支持JsonView视图的继承关系

参考文章
Swagger 自定义Model、Enum(SpringFox源码分析)
springfox 源码分析(一) 程序入口(整个专栏)

用到的依赖

        <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>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>

功能实现
把下面三个类复制到项目中即可生效

OperationJsonViewModelProvider.java

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeBindings;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.classmate.types.ResolvedInterfaceType;
import com.fasterxml.classmate.types.ResolvedObjectType;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.Extension;
import io.swagger.annotations.ExtensionProperty;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.SignatureAttribute.ClassSignature;
import javassist.bytecode.SignatureAttribute.ClassType;
import javassist.bytecode.SignatureAttribute.TypeArgument;
import javassist.bytecode.SignatureAttribute.TypeParameter;
import javassist.bytecode.SignatureAttribute.TypeVariable;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.Types;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationModelsProviderPlugin;
import springfox.documentation.spi.service.contexts.RequestMappingContext;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 *
 * 对象使用jackson进行json序列化和反序列化时,能通过@JsonView设置要屏蔽的字段
 * 用@JsonView修饰接口的参数和返回值,在接口请求和响应时,相关字段会被屏蔽
 * 但swagger不能识别@JsonView,在接口文档中,应该被屏蔽的字段仍会展示出来
 * 此代码作用就是让swagger能识别JsonView注解,正确渲染字段
 *
 * 原理:在swagger扫描接口时,用javassist复制原本的参数和返回值的Class生成
 * 新的Class,跳过要忽略的字段,然后把新的Class注册到swagger中
 *
 * 复杂的字段也能复制,支持泛型类、泛型字段、继承的字段、嵌套对象
 *
 * 举个例子:被@JsonView(UserSimpleView.class)修饰的User类有username、
 * birthday、guardian三个字段,其中birthday字段不参与json序列化,就生成
 * 一个新Class叫UserSimpleView_User,UserSimpleView_User只有username、
 * guardian两个字段,用UserSimpleView_User代替User
 *
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 依赖中的OperationDynamicModelProvider类
 *
 * @author honeypie
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 12)
public class OperationJsonViewModelProvider implements OperationModelsProviderPlugin {

    /**
     * 缓存处理后的数据类型,key是接口的url,value是参数和返回值的ResolvedType
     * key的生成规则在下面的代码中描述
     * 在JsonViewRequestModelReader和JsonViewResponseModelReader中读取缓存
     */
    static HashMap<String, ResolvedType> urlModelRefCache = new HashMap<>();

    /**
     * 把Class转换为ResolvedType
     */
    private final TypeResolver typeResolver;
    /**
     * 把Class转换为BeanDescription,BeanDescription能处理多态,整合类的视图和父类属性
     */
    private final SerializationConfig serializationConfig;
    /**
     * 缓存,key是Class,value是Class对应的BeanDescription
     */
    private final HashMap<Class, BeanDescription> beanDescriptionCache;
    /**
     * 分析对象的json serializer
     */
    private final DefaultSerializerProvider serializerProvider;
    /**
     * javassist的classPool
     */
    private final ClassPool classPool;
    /**
     * 缓存,key是新Class的类全名,value是ResolvedType
     * key的生成规则在下面的代码中描述
     */
    private final HashMap<String, ResolvedType> resolvedTypeCache;

    @Autowired
    public OperationJsonViewModelProvider(TypeResolver typeResolver, ObjectMapper objectMapper) {
        urlModelRefCache = new HashMap<>();
        this.typeResolver = typeResolver;
        this.serializationConfig = objectMapper.getSerializationConfig();
        serializerProvider = ((DefaultSerializerProvider) objectMapper.getSerializerProvider()).createInstance(this.serializationConfig, objectMapper.getSerializerFactory());
        classPool = new ClassPool(null);
        classPool.appendSystemPath();
        beanDescriptionCache = new HashMap<>();
        resolvedTypeCache = new HashMap<>();
    }

    @Override
    public void apply(RequestMappingContext context) {
        // pattern是接口url,比如:/addUser
        String pattern = context.getPatternsCondition().getPatterns().toArray()[0].toString();
        // method是请求类型,比如:[POST]
        String method = context.getMethodsCondition().toString();
        // 拼接在一起就是/addUser[POST]
        String requestMappingPattern = pattern + method;

        // 处理返回值
        collectFromReturnType(context, requestMappingPattern);
        // 处理请求参数
        collectParameters(context, requestMappingPattern);
    }

    /**
     * 处理返回值类型
     * @param context context
     * @param requestMappingPattern 接口url
     */
    private void collectFromReturnType(RequestMappingContext context, String requestMappingPattern) {
        ResolvedType returnType = context.getReturnType();
        Optional<JsonView> jsonViewOptional = context.findAnnotation(JsonView.class);
        if (!Types.isVoid(returnType) && jsonViewOptional.isPresent()) {
            // value是JsonView注解的value,是Class对象,相当于视图名称
            Class[] value = jsonViewOptional.get().value();
            if (value.length > 0) {
                // 生成新的返回值类型
                ResolvedType resolvedType = applyJsonViewModelType(returnType, value[0]);
                // 把新的返回值类型添加到context中
                context.operationModelsBuilder().addReturn(resolvedType);
                // 添加缓存,直接使用requestMappingPattern作为key,比如:/addUser[POST]
                urlModelRefCache.put(requestMappingPattern, resolvedType);
            }
        }
    }

    /**
     * 处理参数类型
     * @param context context
     * @param requestMappingPattern 接口url
     */
    private void collectParameters(RequestMappingContext context, String requestMappingPattern) {
        List<ResolvedMethodParameter> parameters = context.getParameters();
        int paramNum = parameters.size();
        // 遍历接口的参数
        for (int i = 0; i < paramNum; i++) {
            ResolvedMethodParameter parameterType = parameters.get(i);
            Optional<JsonView> jsonViewOptional = parameterType.findAnnotation(JsonView.class);
            if (jsonViewOptional.isPresent()) {
                // value是JsonView注解的value,是Class对象,相当于视图名称
                Class[] value = jsonViewOptional.get().value();
                if (value.length > 0) {
                    // 生成新的参数类型
                    ResolvedType resolvedType = applyJsonViewModelType(parameterType.getParameterType(), value[0]);
                    // 把新的参数类型添加到context中
                    context.operationModelsBuilder().addInputParam(resolvedType);
                    // 添加缓存,用url拼接参数index作为key,比如:/addUser[POST]_P[0]
                    String resolvedTypeKey = requestMappingPattern + "_P[" + i + "]";
                    urlModelRefCache.put(resolvedTypeKey, resolvedType);
                }
            }
        }
    }

    /**
     * 根据原类型originType和JsonView视图名称targetView生成新的类型
     *
     * @param originType 原类型
     * @param targetView 视图名称
     * @return 新类型
     */
    private ResolvedType applyJsonViewModelType(ResolvedType originType, Class targetView) {

        // 生成新Class的类名
        String className = getClassNameWithTypeBindings(originType, targetView);
        Class newClass = null;

        Class erasedClass = originType.getErasedType();
        if (checkIgnorableType(erasedClass)) {
            // 先检查缓存
            if (resolvedTypeCache.containsKey(className)) {
                return resolvedTypeCache.get(className);
            }

            try {
                // 生成类名并创建Class
                String erasedClassName = getClassNameWithoutTypeBindings(erasedClass, targetView);
                newClass = generateModelClass(erasedClassName, erasedClass, targetView);
            } catch (CannotCompileException | NotFoundException | NullPointerException e) {
                e.printStackTrace();
            }
        }

        // 检查原类型的泛型,比如:List<User>,List不用管,只需处理User类
        List<ResolvedType> typeParameters = new ArrayList<>();
        for (ResolvedType typeParameter : originType.getTypeBindings().getTypeParameters()) {
            typeParameters.add(applyJsonViewModelType(typeParameter, targetView));
        }

        // 如果原类型是基本类型,则newClass为null,泛型也为空
        if (newClass == null && typeParameters.isEmpty()) {
            return originType;
        }

        // 检查原类型实现的接口,用于生成ResolvedType
        List<ResolvedType> superInterfaces = new ArrayList<>();
        for (ResolvedType anInterface : originType.getImplementedInterfaces()) {
            superInterfaces.add(applyJsonViewModelType(anInterface, targetView));
        }

        TypeBindings typeBindings = TypeBindings.create(erasedClass, typeParameters);
        Class erased = newClass == null ? erasedClass : newClass;

        ResolvedType newType;
        if (originType.findSupertype(Object.class) == null) {
            // 比如originType是List<User>,会走true的逻辑
            newType = new ResolvedInterfaceType(erased, typeBindings, superInterfaces.toArray(new ResolvedType[0]));
        } else {
            // 比如originType是User,会走false的逻辑
            newType = ResolvedObjectType.create(erased, typeBindings, typeResolver.resolve(typeBindings, erased.getSuperclass()), superInterfaces);
        }

        // 生成ResolvedType并填入缓存
        resolvedTypeCache.put(className, newType);
        return newType;
    }

    /**
     * 判断原类型erasedClass是否需要处理
     * 比如:String类型不用管,User类型需要处理
     * @param erasedClass 原类型Class
     * @return 需要处理就返回true,否则返回false
     */
    private boolean checkIgnorableType(Class erasedClass) {
        try {
            JavaType javaType = serializationConfig.constructType(erasedClass);
            if (!(javaType instanceof SimpleType)) {
                return false;
            }
            JsonSerializer<Object> typedValueSerializer = serializerProvider.findTypedValueSerializer(javaType, true, null);
            return typedValueSerializer instanceof BeanSerializer;
        } catch (JsonMappingException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据类名className判断Class是否存在
     * @param className 类名
     * @param erasedClass 原类型
     * @param targetView 视图名称
     * @throws CannotCompileException 异常
     * @throws NotFoundException 异常
     */
    private void prepareModelClass(String className, Class erasedClass, Class targetView) throws CannotCompileException, NotFoundException {
        try {
            // 如果当前线程的classPool中存在,就不用生成
            Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            try {
                // 如果javassist的classPool中存在,就不用生成
                classPool.get(className);
            } catch (NotFoundException e2) {
                // 生成Class
                generateModelClass(className, erasedClass, targetView);
            }
        }
    }

    /**
     * 根据原类型erasedClass和视图名称targetView生成新Class
     * @param className 类全名
     * @param erasedClass 原类型Class
     * @param targetView 视图名称Class
     * @return 新Class
     * @throws CannotCompileException 异常
     * @throws NotFoundException 异常
     */
    private Class generateModelClass(String className, Class erasedClass, Class targetView) throws CannotCompileException, NotFoundException {
        CtClass ctClass;
        try {
            // 判断当前线程的classPool中是否存在
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            // 使用javassist生成新Class
            ctClass = classPool.makeClass(className);
        }

        // 复制原类型的泛型参数
        java.lang.reflect.TypeVariable[] sourceArr = erasedClass.getTypeParameters();
        TypeParameter[] targetArr = new TypeParameter[sourceArr.length];
        for (int i = 0; i < sourceArr.length; i++) {
            targetArr[i] = new TypeParameter(sourceArr[i].getName());
        }
        ctClass.setGenericSignature(new ClassSignature(targetArr).encode());

        // 获取原类型的BeanDescription
        BeanDescription beanDesc = beanDescriptionCache.get(erasedClass);
        if(beanDesc == null) {
            beanDesc = serializationConfig.introspect(serializationConfig.constructType(erasedClass));
            beanDescriptionCache.put(erasedClass, beanDesc);
        }

        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        // 复制原类型的ApiModel注解以及参数
        ApiModel apiModel = beanDesc.getClassAnnotations().get(ApiModel.class);
        if (apiModel != null) {
            AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
            Annotation ann = new Annotation(ApiModel.class.getName(), constPool);
            ann.addMemberValue("value", new StringMemberValue(apiModel.value(), constPool));
            ann.addMemberValue("description", new StringMemberValue(apiModel.description(), constPool));
            ann.addMemberValue("parent", new ClassMemberValue(apiModel.parent().getName(), constPool));
            ann.addMemberValue("discriminator", new StringMemberValue(apiModel.discriminator(), constPool));
            ArrayMemberValue targetSubTypes = new ArrayMemberValue(constPool);
            Class<?>[] classes = apiModel.subTypes();
            ClassMemberValue[] sourceSubTypes = new ClassMemberValue[classes.length];
            for (int i = 0; i < classes.length; i++) {
                sourceSubTypes[i] = new ClassMemberValue(classes[i].getName(), constPool);
            }
            targetSubTypes.setValue(sourceSubTypes);
            ann.addMemberValue("subTypes", targetSubTypes);
            ann.addMemberValue("reference", new StringMemberValue(apiModel.reference(), constPool));
            attr.addAnnotation(ann);
            classFile.addAttribute(attr);
        }

        // defaultView是指声明在类上的@JsonView视图名称
        Class<?>[] defaultViews = beanDesc.findDefaultViews();
        boolean hasDefaultView = defaultViews != null && defaultViews.length > 0;

        for (BeanPropertyDefinition property : beanDesc.findProperties()) {
            // 遍历原类型的每一个property,根据视图关系判断是否展示

            boolean propertyVisible = false;

            JsonView JsonViewAnn = property.getAccessor().getAnnotation(JsonView.class);
            if (JsonViewAnn != null) {
                for (Class<?> fieldView : JsonViewAnn.value()) {
                    if (fieldView.isAssignableFrom(targetView)) {
                        propertyVisible = true;
                        break;
                    }
                }
            } else if (hasDefaultView) {
                for (Class<?> defaultView : defaultViews) {
                    if (defaultView.isAssignableFrom(targetView)) {
                        propertyVisible = true;
                        break;
                    }
                }
            }

            if (propertyVisible) {
                // 如果property需要展示,就移植到新Class

                Class fieldType = property.getRawPrimaryType();
                CtClass fieldCtClass;

                if (checkIgnorableType(fieldType)) {
                    String fieldTypeName = getClassNameWithoutTypeBindings(fieldType, targetView);
                    prepareModelClass(fieldTypeName, fieldType, targetView);
                    fieldCtClass = classPool.getCtClass(fieldTypeName);
                } else {
                    fieldCtClass = classPool.getCtClass(fieldType.getName());
                }

                CtField ctField = new CtField(fieldCtClass, property.getName(), ctClass);
                ctField.setModifiers(Modifier.PUBLIC);

                Type fieldGenericType;
                AnnotatedMember member = property.getPrimaryMember();
                if (member instanceof AnnotatedField) {
                    fieldGenericType = ((AnnotatedField) member).getAnnotated().getGenericType();
                } else if (member instanceof AnnotatedMethod) {
                    fieldGenericType = ((AnnotatedMethod) member).getAnnotated().getGenericReturnType();
                } else {
                    continue;
                }

                // 如果字段类型是基本类型,则fieldType.isPrimitive()为true
                if (!fieldType.isPrimitive()) {
                    // 不是基本类型的字段,需要设置GenericSignature
                    ctField.setGenericSignature(getTypeGenericSignature(fieldGenericType, targetView).getType().encode());
                }

                // 复制原字段的ApiModelProperty注解以及参数
                ApiModelProperty apiModelProperty = property.getPrimaryMember().getAnnotation(ApiModelProperty.class);
                if (apiModelProperty != null) {
                    AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                    Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                    ann.addMemberValue("value", new StringMemberValue(apiModelProperty.value(), constPool));
                    ann.addMemberValue("name", new StringMemberValue(apiModelProperty.name(), constPool));
                    ann.addMemberValue("allowableValues", new StringMemberValue(apiModelProperty.allowableValues(), constPool));
                    ann.addMemberValue("access", new StringMemberValue(apiModelProperty.access(), constPool));
                    ann.addMemberValue("notes", new StringMemberValue(apiModelProperty.notes(), constPool));
                    ann.addMemberValue("dataType", new StringMemberValue(apiModelProperty.dataType(), constPool));
                    ann.addMemberValue("required", new BooleanMemberValue(apiModelProperty.required(), constPool));
                    IntegerMemberValue position = new IntegerMemberValue(constPool);
                    position.setValue(apiModelProperty.position());
                    ann.addMemberValue("position", position);
                    ann.addMemberValue("hidden", new BooleanMemberValue(apiModelProperty.hidden(), constPool));
                    ann.addMemberValue("example", new StringMemberValue(apiModelProperty.example(), constPool));
                    ann.addMemberValue("readOnly", new BooleanMemberValue(apiModelProperty.readOnly(), constPool));
                    EnumMemberValue enumMemberValue = new EnumMemberValue(constPool);
                    enumMemberValue.setValue(apiModelProperty.accessMode().name());
                    enumMemberValue.setType(ApiModelProperty.AccessMode.class.getName());
                    ann.addMemberValue("accessMode", enumMemberValue);
                    ann.addMemberValue("reference", new StringMemberValue(apiModelProperty.reference(), constPool));
                    ann.addMemberValue("allowEmptyValue", new BooleanMemberValue(apiModelProperty.allowEmptyValue(), constPool));
                    ArrayMemberValue targetExtensions = new ArrayMemberValue(constPool);
                    Extension[] extensions = apiModelProperty.extensions();
                    AnnotationMemberValue[] sourceExtensions = new AnnotationMemberValue[extensions.length];
                    for (int i = 0; i < extensions.length; i++) {
                        Extension extension = extensions[i];
                        Annotation ann1 = new Annotation(Extension.class.getName(), constPool);
                        ann1.addMemberValue("name", new StringMemberValue(extension.name(), constPool));
                        ArrayMemberValue targetProperties = new ArrayMemberValue(constPool);
                        ExtensionProperty[] properties = extension.properties();
                        AnnotationMemberValue[] sourceProperties = new AnnotationMemberValue[properties.length];
                        for (int i1 = 0; i1 < properties.length; i1++) {
                            ExtensionProperty extensionProperty = properties[i];
                            Annotation ann2 = new Annotation(ExtensionProperty.class.getName(), constPool);
                            ann2.addMemberValue("name", new StringMemberValue(extensionProperty.name(), constPool));
                            ann2.addMemberValue("value", new StringMemberValue(extensionProperty.value(), constPool));
                            sourceProperties[i] = new AnnotationMemberValue(ann2, constPool);
                        }
                        targetProperties.setValue(sourceProperties);
                        ann1.addMemberValue("properties", targetProperties);
                        sourceExtensions[i] = new AnnotationMemberValue(ann1, constPool);
                    }
                    targetExtensions.setValue(sourceExtensions);
                    ann.addMemberValue("extensions", targetExtensions);
                    attr.addAnnotation(ann);
                    ctField.getFieldInfo().addAttribute(attr);
                }

                ctClass.addField(ctField);
            }
        }

        return ctClass.toClass();
    }

    /**
     * 用javassist生成CtClass时,需要设置CtField类型
     * 根据字段类型type和视图名称targetView生成字段的GenericSignature
     *
     * @param type 对象的字段类型
     * @param targetView 视图名称
     * @return TypeArgument CtField的GenericSignature
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private TypeArgument getTypeGenericSignature(Type type, Class targetView) throws CannotCompileException, NotFoundException {
        // 字段类型分三种情况处理

        if (type instanceof java.lang.reflect.TypeVariable) {
            // 泛型字段,比如:private T t;
            TypeVariable ctFieldTypeVariable = new TypeVariable(type.getTypeName());
            return new TypeArgument(ctFieldTypeVariable);
        } else if (type instanceof ParameterizedType) {
            // 字段类型包含泛型,比如:private List<User> list;
            ParameterizedType parameterizedType = (ParameterizedType) type;
            TypeArgument rawType = getTypeGenericSignature(parameterizedType.getRawType(), targetView);

            Type[] sourceArr = parameterizedType.getActualTypeArguments();
            TypeArgument[] targetArr = new TypeArgument[sourceArr.length];
            for (int i = 0; i < sourceArr.length; i++) {
                targetArr[i] = getTypeGenericSignature(sourceArr[i], targetView);
            }
            return new TypeArgument(new ClassType(((ClassType) rawType.getType()).getName(), targetArr));
        } else if (type instanceof Class) {
            // 普通类型字段,比如:private User user;
            Class classType = (Class) type;

            String rowTypeName;
            if (checkIgnorableType(classType)) {
                rowTypeName = getClassNameWithoutTypeBindings(classType, targetView);
                prepareModelClass(rowTypeName, classType, targetView);
            } else {
                rowTypeName = classType.getTypeName();
            }

            return new TypeArgument(new ClassType(rowTypeName));
        } else {
            throw new RuntimeException("unknown field type : " + type.getTypeName());
        }
    }

    /**
     * 根据原类型type和视图名称targetView,生成新类型名称,会忽略泛型
     * 比如原类型是com.swagger.demo.entity.User<String>,视图名称是UserSimpleView
     * 新类型名称就是com.swagger.demo.entity.UserSimpleView_User
     *
     * @param type 原类型
     * @param targetView 视图名称
     * @return 新类型名称
     */
    private String getClassNameWithoutTypeBindings(Class type, Class targetView) {
        return type.getPackage().getName() + "." + targetView.getSimpleName() + "_" + type.getSimpleName();
    }

    /**
     * 根据原类型type和视图名称targetView,生成新类型名称,会保留泛型
     * 比如原类型是com.swagger.demo.entity.User<String>,视图名称是UserSimpleView
     * 新类型名称就是com.swagger.demo.entity.UserSimpleView_User<String>
     *
     * @param type 原类型
     * @param targetView 视图名称
     * @return 新类型名称
     */
    private String getClassNameWithTypeBindings(ResolvedType type, Class<?> targetView) {
        StringBuilder className = new StringBuilder();
        className.append(type.getErasedType().getPackage().getName())
                .append(".")
                .append(targetView.getSimpleName())
                .append("_");

        appendTypeName(type, className);
        return className.toString();
    }

    private void appendTypeName(ResolvedType type, StringBuilder className) {
        className.append(type.getErasedType().getSimpleName());
        TypeBindings typeBindings = type.getTypeBindings();
        if (typeBindings.size() > 0) {
            className.append("<");
            int size = typeBindings.size();
            for (int i = 0; i < size; ) {
                appendTypeName(typeBindings.getBoundType(i), className);
                if (++i < size) {
                    className.append(",");
                }
            }
            className.append(">");
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

JsonViewRequestModelReader.java

import com.fasterxml.classmate.ResolvedType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;

import static springfox.documentation.schema.ResolvedTypes.modelRefFactory;
import static springfox.documentation.spi.schema.contexts.ModelContext.inputParam;

/**
 * @author honeypie
 * 把解析后的请求值类型填充到swagger文档中
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 此依赖中的DynamicParameterBuilderPlugin类
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class JsonViewRequestModelReader implements ParameterBuilderPlugin {


    private final TypeNameExtractor typeNameExtractor;

    @Autowired
    public JsonViewRequestModelReader(TypeNameExtractor typeNameExtractor) {
        this.typeNameExtractor = typeNameExtractor;
    }

    @Override
    public void apply(ParameterContext context) {
        ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();

        OperationContext operationContext = context.getOperationContext();
        String pattern = operationContext.requestMappingPattern();
        String method = "[" + operationContext.httpMethod().name() + "]";
        String requestMappingPattern = pattern + method;
        // 拼接url、method和参数index得到缓存的key,比如:/addUser[POST]_P[0]
        requestMappingPattern = requestMappingPattern + "_P[" + methodParameter.getParameterIndex() + "]";

        if (OperationJsonViewModelProvider.urlModelRefCache.containsKey(requestMappingPattern)) {
            ResolvedType parameterType = OperationJsonViewModelProvider.urlModelRefCache.get(requestMappingPattern);
            ModelContext modelContext = inputParam(
                    context.getGroupName(),
                    parameterType,
                    context.getDocumentationType(),
                    context.getAlternateTypeProvider(),
                    context.getGenericNamingStrategy(),
                    context.getIgnorableParameterTypes());
            ModelReference modelRef = modelRefFactory(modelContext, typeNameExtractor).apply(parameterType);

            context.parameterBuilder()
                    .type(parameterType)
                    .modelRef(modelRef);
        }

    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

}

JsonViewResponseModelReader.java

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.jackson.annotation.JsonView;
import com.google.common.base.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.schema.Types;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import static com.google.common.collect.Sets.newHashSet;
import static springfox.documentation.schema.ResolvedTypes.modelRefFactory;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.httpStatusCode;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.message;

/**
 * @author honeypie
 * 把解析后的response返回值类型填充到swagger文档中
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 此依赖中的DynamicResponseModelReader类
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1051)
public class JsonViewResponseModelReader implements OperationBuilderPlugin {

    private final TypeNameExtractor typeNameExtractor;

    @Autowired
    public JsonViewResponseModelReader(TypeNameExtractor typeNameExtractor) {
        this.typeNameExtractor = typeNameExtractor;
    }

    @Override
    public void apply(OperationContext context) {
        ResolvedType returnType = context.getReturnType();
        if (Types.isVoid(returnType)) return;

        Optional<JsonView> jsonViewOptional = context.findAnnotation(JsonView.class);
        if (jsonViewOptional.isPresent()) {
            Class<?>[] value = jsonViewOptional.get().value();
            if (value.length > 0) {

                String requestMappingPattern = context.requestMappingPattern();
                String method = "[" + context.httpMethod().name() + "]";
                // 拼接url和method得到缓存的key,比如:/addUser[POST]
                returnType = OperationJsonViewModelProvider.urlModelRefCache.get(requestMappingPattern + method);
                if (returnType == null) {
                    return;
                }

                int httpStatusCode = httpStatusCode(context);
                String message = message(context);

                ModelContext modelContext = ModelContext.returnValue(
                        context.getGroupName(),
                        returnType,
                        context.getDocumentationType(),
                        context.getAlternateTypeProvider(),
                        context.getGenericsNamingStrategy(),
                        context.getIgnorableParameterTypes());

                ModelReference modelRef = modelRefFactory(modelContext, typeNameExtractor).apply(returnType);

                ResponseMessage built = new ResponseMessageBuilder()
                        .code(httpStatusCode)
                        .message(message)
                        .responseModel(modelRef)
                        .build();

                context.operationBuilder().responseMessages(newHashSet(built));
            }
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

Swagger 是一种用于构建、设计和文档化 RESTful API 的工具。它提供了一个简单易用的界面,让开发者可以定义 API 的各种细节,包括端点、参数、请求和响应等。Swagger 还可以根据用户的定义自动生成 OpenAPI JSON,这是一种机器可读的 API 文档格式。 Swagger 生成 OpenAPI JSON 的过程非常简单。首先,我们需要在 Swagger 的配置文件或注解中定义我们的 API。在定义 API 时,我们需要指定每个端点的路径、请求方法、请求参数、请求体和响应内容等。为了使生成的 OpenAPI JSON 更加准确和完整,我们还可以添加其他的元数据,比如 API 的标题、描述、版本号等。 当我们完成 API 的定义后,我们可以使用 Swagger 提供的工具自动生成 OpenAPI JSON。通常,我们只需要运行一条命令或点击一个按钮,Swagger 就会根据我们的定义扫描我们的 API,并生成一份符合 OpenAPI 规范的 JSON 文件。这份文件包含了 API 的详细信息,包括端点的路径、请求方法、请求和响应的参数等。生成的 JSON 文件可以供其他开发者和工具使用,比如用于生成文档、进行代码生成等。 总之,Swagger 是一个强大的工具,可以帮助我们快速构建、设计和文档化 RESTful API。通过定义 API,并使用 Swagger 提供的工具生成 OpenAPI JSON,我们可以更方便地与其他开发者进行协作,并提供高质量的 API 文档。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值