当前台每次请求需要的字段不一样,但是我们还不想把多余的字段暴露给前台的时候,我们肯定需要重服创建琐碎的字段,可能因为前台多要了一个字段又要创建一个新的实体类,项目里多余又难看,那该怎么办呢,用用以下方式:
这是自定义的两个注解我拷贝在下面,我也是自己找的,大家需要自己拷贝吧;
注解@ApiGlobalModel的代码:
package com.dpi.service.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiGlobalModel {
Class> component();
String separator() default ",";
String[] value() default {};
}
处理注解@ApiGlobalModel逻辑的代码:
package com.dpi.service.annotation.impl;
import com.dpi.service.annotation.ApiGlobalModel;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.MemberValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
@Order
@Slf4j
public class ApiGlobalModelBuilder implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
private final static String BASE_PACKAGE = "com.swagger.json.entity.";
private final static String DEFAULT_CLASS_NAME = "ParameterGlobalBO";
private static Integer i = 1;
@Override
public void apply(ParameterContext context) {
try {
// 从方法或参数上获取指定注解的Optional
Optional optional = context.getOperationContext().findAnnotation(ApiGlobalModel.class);
if (!optional.isPresent()) {
optional = context.resolvedMethodParameter().findAnnotation(ApiGlobalModel.class);
}
if (optional.isPresent()) {
String key = DEFAULT_CLASS_NAME + i++;
ApiGlobalModel apiAnno = optional.get();
try {
//类名重复将导致swagger识别不准确 主动触发异常
Class.forName(BASE_PACKAGE + key);
} catch (ClassNotFoundException e) {
String[] fields = apiAnno.value();
String separator = apiAnno.separator();
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(BASE_PACKAGE + key);
ctClass.setModifiers(Modifier.ABSTRACT);
//处理 javassist.NotFoundException
pool.insertClassPath(new ClassClassPath(apiAnno.component()));
CtClass globalCtClass = pool.getCtClass(apiAnno.component().getName());
//从globalCtClass拷贝指定字段到动态创建的类中
for (String field : merge(fields, separator)) {
//若指定的字段不存在 throw NotFoundException
CtField ctField = globalCtClass.getDeclaredField(field);
CtField newCtField = new CtField(ctField, ctClass);
handleField(newCtField);
ctClass.addField(newCtField);
}
// 将生成的Class添加到SwaggerModels
context.getDocumentationContext().getAdditionalModels()
.add(typeResolver.resolve(ctClass.toClass()));
// 修改Json参数的ModelRef为动态生成的class
context.parameterBuilder()
.parameterType("body").modelRef(new ModelRef(key)).name("body").description("body");
}
}
} catch (Exception e) {
log.error("@ApiGlobalModel Error", e);
}
}
private void handleField(CtField field) {
//防止private又没有getter
field.setModifiers(Modifier.PUBLIC);
//有name的把字段名改为name
//因为JSON格式化的原因,ApiModelProperty的name属性无效 所以如果有name,直接更改字段名为name
AnnotationsAttribute annos = ((AnnotationsAttribute) field.getFieldInfo().getAttribute("RuntimeVisibleAnnotations"));
if (annos != null) {
Annotation anno = annos.getAnnotation(ApiModelProperty.class.getTypeName());
if (anno != null) {
MemberValue name = anno.getMemberValue("name");
if (name != null) {
//这里返回的name会以引号包裹
String fName = name.toString().replace(""", "").trim();
if (fName.length() > 0) {
field.setName(fName);
}
}
}
}
}
private List merge(String[] arr, String separator) {
List tmp = new ArrayList<>();
Arrays.stream(arr).forEach(s -> {
if (s.contains(separator)) {
tmp.addAll(Arrays.asList(s.split(separator)));
} else {
tmp.add(s);
}
});
return tmp;
}
@Override
public boolean supports(DocumentationType documentationType) {
return true;
}
}
注解@ApiJsonModel的代码:
package com.dpi.service.annotation;
import io.swagger.annotations.ApiModelProperty;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonModel {
ApiModelProperty[] value() default {};
}
处理@ApiJsonModel的代码:
package com.dpi.service.annotation.impl;
import com.dpi.service.annotation.ApiJsonModel;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@Component
@Order
@Slf4j
public class ApiJsonModelBuilder implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
private final static String BASE_PACKAGE = "com.swagger.json.entity.";
private final static String DEFAULT_CLASS_NAME = "ParameterJsonBO";
private static Integer i = 1;
@Override
public void apply(ParameterContext context) {
try {
// 从方法或参数上获取指定注解的Optional
Optional optional = context.getOperationContext().findAnnotation(ApiJsonModel.class);
if (!optional.isPresent()) {
optional = context.resolvedMethodParameter().findAnnotation(ApiJsonModel.class);
}
if (optional.isPresent()) {
String key = DEFAULT_CLASS_NAME + i++;
ApiJsonModel apiAnno = optional.get();
try {
Class.forName(BASE_PACKAGE + key);
} catch (ClassNotFoundException e) {
ApiModelProperty[] properties = apiAnno.value();
// 将生成的Class添加到SwaggerModels
context.getDocumentationContext().getAdditionalModels()
.add(typeResolver.resolve(createRefModel(properties, key)));
// 修改Json参数的ModelRef为动态生成的class
context.parameterBuilder()
.parameterType("body").modelRef(new ModelRef(key)).name("JSON").description("body");
}
}
} catch (Exception e) {
log.error("@ApiJsonModel Error", e);
}
}
private Class> createRefModel(ApiModelProperty[] propertys, String key) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(BASE_PACKAGE + key);
ctClass.setModifiers(Modifier.ABSTRACT);
for (ApiModelProperty property : propertys) {
ctClass.addField(createField(property, ctClass));
}
return ctClass.toClass();
}
private CtField createField(ApiModelProperty property, CtClass ctClass) throws Exception {
//此处默认字段类型为String 如果不是 swagger也是取注解的dataType 字段类型就不重要了
CtField ctField = new CtField(ClassPool.getDefault().get(String.class.getName()), property.name(), ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ConstPool constPool = ctClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
//不知道是否有直接把property转为对应Annotation的方法 它们本质是一样的
Annotation anno = new Annotation(ApiModelProperty.class.getTypeName(), constPool);
Method[] members = ApiModelProperty.class.getDeclaredMethods();
for (Method member : members) {
Object value = member.invoke(property);
//使用默认值的就不重复赋值了
if (!value.equals(member.getDefaultValue())) {
//由于只拷贝了String,int,boolean等返回值的属性,所以诸如accessMode,extensions这样的属性设置将无效
Type type = member.getReturnType();
if (type == String.class) {
anno.addMemberValue(member.getName(), new StringMemberValue(String.valueOf(member.invoke(property)), constPool));
} else if (type == int.class) {
anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
} else if (type == Long.class) {
anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
}else if (type == Integer.class) {
anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
}else if (type == boolean.class) {
anno.addMemberValue(member.getName(), new BooleanMemberValue((Boolean) member.invoke(property), constPool));
}
}
}
attr.addAnnotation(anno);
ctField.getFieldInfo().addAttribute(attr);
return ctField;
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
以下是使用方法:
@ApiGlobalModel注解说明:对入参json形式 注解用于从一个已有的实体类中抽取接口所需的参数字
使用方法:
需要什么暴露什么字段,value后面写好参数以逗号分割对应实体类,注意大小写;
接下来swagger页面就是这样:
@ApiJsonModel注解说明:对入参json形式 对入参进行说明
使用方法:
这是我们需要暴露给前台的字段,控制层我们用JSONObject,后期我们后台代码处理随意转换对象类型;
接下来swagger页面就是这样:
以下注意事项:
我这里创建了一个空的控制层以Z字母开头的,大家以后创建控制层不要以Z开头命名,因为这三个注解按a-z顺序加载,最后一个加载不到,这个是swagger的bug,所以要建一个空控制器以Z开头命名,自己业务代码控制器最好就别以z开头命名,就这一个要求;



