拓展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.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;
}
}