拓展Swagger功能,支持JsonView视图

拓展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功能,支持JsonView视图

增强后的swagger文档
返回值的birthday字段消失了
(右边是swagger-bootstrap-ui的界面)
拓展Swagger功能,支持JsonView视图

原理
在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.classmate.TypeResolver;
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, TypeResolver typeResolver) {
        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;
    }
}

上一篇:OPPO AI安全竞赛-人脸识别攻防


下一篇:Swagger常用注解实验