本文的实现是依赖于这一篇博客,原文传送门点击这里!
@Target({
ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DaisyApiIgnoreProps {
/**
* 忽略的属性值
* @return
*/
String[] value() default {
};
}
@Target({
ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DaisyApiNeedProps {
/**
* 需要的属性值
* @return
*/
String[] value() default {
};
}
package daisy.framework.api.config.plugins;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import daisy.framework.api.annotations.DaisyApiIgnoreProps;
import daisy.framework.api.annotations.DaisyApiNeedProps;
import io.swagger.annotations.ApiModelProperty;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
import org.apache.ibatis.javassist.bytecode.ConstPool;
import org.apache.ibatis.javassist.bytecode.annotation.Annotation;
import org.apache.ibatis.javassist.bytecode.annotation.BooleanMemberValue;
import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Order
@Component
public class SwaggerModelReader implements ParameterBuilderPlugin {
private TypeResolver typeResolver;
private static final Logger logger = LoggerFactory.getLogger(SwaggerModelReader.class);
@Autowired
public void setTypeResolver(TypeResolver typeResolver) {
this.typeResolver = typeResolver;
}
@Override
public void apply(ParameterContext parameterContext) {
ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
Optional<DaisyApiIgnoreProps> ignorePropsOptional = methodParameter.findAnnotation(DaisyApiIgnoreProps.class);
Optional<DaisyApiNeedProps> needPropsOptional = methodParameter.findAnnotation(DaisyApiNeedProps.class);
String apiUrl = parameterContext.getOperationContext().requestMappingPattern();
if (ignorePropsOptional.isPresent() && needPropsOptional.isPresent()) {
// 2个注解不能同时存在于一个参数上
StringBuilder errorMsg = new StringBuilder("@DaisyApiIgnoreProps with @DaisyApiNeedProps cannot exist at the same time! ");
errorMsg.append("api路径:").append(apiUrl).append("\t");
Optional<String> defaultName = parameterContext.resolvedMethodParameter().defaultName();
if (defaultName.isPresent()) {
errorMsg.append("参数名:").append(defaultName.get()).append("\t");
}
// 写一个错误日志 但是不影响启动 因为这个插件并不会影响到系统的正常运行
logger.error(errorMsg.toString());
}
if (ignorePropsOptional.isPresent() || needPropsOptional.isPresent()) {
// 如果是泛型的话 那么获取泛型里面的数据
Class originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();
boolean isCollection = Collection.class.isAssignableFrom(originClass);
// 如果参数是集合类型的话 那么原始类应该
if (isCollection) {
// 获取泛型类型
originClass = parameterContext.resolvedMethodParameter().getParameterType().getTypeBindings().getBoundType(0).getErasedType();
}
//model 名称
String name = originClass.getSimpleName() + apiUrl.replaceAll("/", "_");
String[] properties;
boolean ignore = ignorePropsOptional.isPresent();
if (ignore) {
properties = ignorePropsOptional.get().value();
} else {
properties = needPropsOptional.get().value();
}
// 向文档内容中添加新的模型对象
parameterContext.getDocumentationContext()
.getAdditionalModels()
//像documentContext的Models中添加我们新生成的Class
.add(typeResolver.resolve(createRefModelIgp(properties, name, originClass, ignore)));
ModelRef modelRef;
if (!isCollection) {
modelRef = new ModelRef(name);
} else {
modelRef = new ModelRef("List", new ModelRef(name));
}
//修改model参数的ModelRef为我们动态生成的class
parameterContext.parameterBuilder()
.parameterType("body")
.modelRef(modelRef)
.name(name);
}
}
/**
* 根据propertys中的值动态生成含有Swagger注解的javaBeen
*/
private Class createRefModelIgp(String[] propertys, String name, Class origin, boolean ignore) {
ClassPool pool = ClassPool.getDefault();
String newClassName = origin.getPackage().getName() + "." + name;
try {
CtClass ctClass = pool.getOrNull(newClassName);
if (ctClass != null) {
// 这行代码很重要 如果没有的话 那就不能和devTool 一起使用
// 销毁旧的类
ctClass.detach();
}
// 通过javassit 技术创建一个类
ctClass = pool.makeClass(origin.getPackage().getName() + "." + name);
// 获取所有的属性
List<Field> fieldList = getAllField(origin, null);
List<String> dealProperties = Arrays.asList(propertys);
List<Field> dealFileds = fieldList.stream().filter(
s -> ignore ? (!(dealProperties.contains(s.getName()))) : dealProperties.contains(s.getName())
).collect(Collectors.toList());
// 创建属性
createCtFileds(dealFileds, ctClass);
return ctClass.toClass();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取一个类中的所以属性
*
* @param clazz
* @param fieldList
* @return
*/
private static List<Field> getAllField(Class clazz, List<Field> fieldList) {
if (fieldList == null) {
fieldList = new ArrayList<>(16);
}
Field[] declaredFields = clazz.getDeclaredFields();
if (declaredFields.length > 0) {
fieldList.addAll(Arrays.asList(declaredFields));
}
// 获取父类属性
Class superclass = clazz.getSuperclass();
if (!superclass.equals(Object.class)) {
// 重复处理父类
fieldList = getAllField(superclass, fieldList);
}
return fieldList;
}
/**
* 通过字节码重新创建一些属性
*
* @param dealFileds
* @param ctClass
* @throws CannotCompileException
* @throws NotFoundException
*/
public void createCtFileds(List<Field> dealFileds, CtClass ctClass) throws CannotCompileException, NotFoundException {
// 获取常量池
ConstPool constPool = ctClass.getClassFile().getConstPool();
for (Field field : dealFileds) {
CtField ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
ctField.setModifiers(Modifier.PUBLIC);
//添加model属性说明
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
if (apiModelProperty != null) {
String[] valNames = new String[]{
"name", "value", "example", "allowableValues", "access", "notes", "dataType", "reference"};
String[] vals = new String[]{
apiModelProperty.name(),
apiModelProperty.value(), apiModelProperty.example(),
apiModelProperty.allowableValues(), apiModelProperty.access(),
apiModelProperty.notes(), apiModelProperty.dataType(), apiModelProperty.reference()};
for (int i = 0; i < valNames.length; i++) {
if (!"".equals(vals[i])) {
ann.addMemberValue(valNames[i], new StringMemberValue(vals[i], constPool));
}
}
// 处理hidden
ann.addMemberValue("hidden", new BooleanMemberValue(apiModelProperty.hidden(), constPool));
}
attr.addAnnotation(ann);
ctField.getFieldInfo().addAttribute(attr);
ctClass.addField(ctField);
}
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
设置需要忽略属性
@PostMapping("/create")
@Override
public DaisyResponseInfo<VO> create(@DaisyApiIgnoreProps({
"id"}) @RequestBody List<VO> vos) {
DaisyResponseInfo<VO> responseInfo = new DaisyResponseInfo<>();
List<VO> newVos = daisyCRUDService.create(vos);
responseInfo.setData(newVos);
return responseInfo;
}
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
import org.apache.ibatis.javassist.bytecode.ConstPool;
import org.apache.ibatis.javassist.bytecode.annotation.Annotation;
import org.apache.ibatis.javassist.bytecode.annotation.BooleanMemberValue;
import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue;
CtClass ctClass = pool.getOrNull(newClassName);
if (ctClass != null) {
// 这行代码很重要 如果没有的话 那就不能和devTool 一起使用
// 销毁旧的类,如果没有销毁,那么在热部署的时候会抛出一个异常
// className : frozen class (cannot edit)
ctClass.detach();
}
@DeleteMapping("/delete")
@Override
public DaisyResponseInfo<VO> delete(@RequestBody List<VO> vos) {
DaisyResponseInfo<VO> responseInfo = new DaisyResponseInfo<VO>();
daisyCRUDService.delete(vos);
return responseInfo;
}
这个时候就要处理泛型了。核心代码如下: // 如果是泛型的话 那么获取泛型里面的数据
Class originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();
boolean isCollection = Collection.class.isAssignableFrom(originClass);
// 如果参数是集合类型的话 那么原始类应该
if (isCollection) {
// 获取泛型类型
originClass = parameterContext.resolvedMethodParameter().getParameterType().getTypeBindings().getBoundType(0).getErasedType();
}
// 省略构建类代码
ModelRef modelRef;
if (!isCollection) {
modelRef = new ModelRef(name);
} else {
// 对于集合类 需要在外面再包一层对象
modelRef = new ModelRef("List", new ModelRef(name));
}
我们通过一大堆代码完成了一个对象被多个api接口使用,并再丝袜哥上显示不同的内容,但是我们回头想一下,它的实现原理其实很简单,在项目启动过程中,如果检测到api接口中使用到的对象添加了自定义注解,那么我们会根据原有对象类型和注解内容给重新生成一个对象,然后再将文档中的构建对象换成我们通过字节码创建的类,以达到狸猫换太子的目的。
对于外层使用者而言只是一个简单的注解和配置几个参数,在开发过程中完全不用关心实现,这不正是框架的魅力吗。
swagger2自定义注解 --为什么总有一个自定义model不能被识别!