相关文章推荐
聪明伶俐的铁板烧  ·  Spring Boot ...·  1 周前    · 
谦虚好学的太阳  ·  Swagger UI "Try it ...·  4 天前    · 
鬼畜的山羊  ·  Can’t SSH to Azure ...·  1 年前    · 
忐忑的牛排  ·  python - Make tick ...·  1 年前    · 
善良的小蝌蚪  ·  Serialized Form ...·  1 年前    · 
  • 接口的请求参数对象中,部分属性是由后端填充的,所以希望在接口api文档中把这些入参隐藏掉。
  • 为了解决上面提到的两个问题,我的第一反应是去网上查资料。但是找了一圈,也没有找到现成的解决方法。故而通过参考knife4j的某些扩展方式自己实现,并把自己的实现方法分享出来,以便有类似需求的小伙儿参考。

    第2个问题其实knife4j的@ApiOperationSupport注解也能实现隐藏请求参数的效果,但是呢它有一个缺陷(或者说是bug),那就是一旦给方法加上该注解之后,如果请求参数中存在嵌套的对象,那么文档中生成的请求示例将会缺少部分参数。

    通过springfox的扩展机制,利用javassist动态创建类的方式来替换原接口的响应参数或请求参数对应的类 用到的扩展点如下:

    // 替换响应结果中的类
    springfox.documentation.spi.service.OperationBuilderPlugin
    // 替换请求参数中的类
    springfox.documentation.spi.service.ParameterBuilderPlugin
    // 将javassist动态生成的类解析为Model,以便在knife4j页面中可以正常展示api文档
    springfox.documentation.spi.service.OperationModelsProviderPlugin
    

    利用以下两种注解提供接口的响应类型,兼容v2/v3

    io.swagger.v3.oas.annotations.responses.ApiResponse
    io.swagger.v3.oas.annotations.responses.ApiResponses
    io.swagger.annotations.ApiResponses
    io.swagger.annotations.ApiResponse
    

    利用自定义注解来提供需要隐藏的参数信息

    响应结果只能通过将状态码定义为类似200-1这种方式,因为所有状态都定义为200,则只会展示一种结果,就达不到目的了。当然这也算是这种解决方式的一个缺陷吧,但总归时实现了将所有响应类型的属性文档均展示出来了,更加方便对接的同学开发使用

    import java.util.Set;
    import java.util.function.Consumer;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtField;
    import javassist.LoaderClassPath;
    import javassist.Modifier;
    import javassist.NotFoundException;
    import javassist.bytecode.AnnotationsAttribute;
    import javassist.bytecode.ClassFile;
    import javassist.bytecode.ConstPool;
    import javassist.bytecode.FieldInfo;
    import javassist.bytecode.annotation.Annotation;
    import javassist.bytecode.annotation.AnnotationMemberValue;
    import javassist.bytecode.annotation.ArrayMemberValue;
    import javassist.bytecode.annotation.BooleanMemberValue;
    import javassist.bytecode.annotation.ByteMemberValue;
    import javassist.bytecode.annotation.CharMemberValue;
    import javassist.bytecode.annotation.ClassMemberValue;
    import javassist.bytecode.annotation.DoubleMemberValue;
    import javassist.bytecode.annotation.EnumMemberValue;
    import javassist.bytecode.annotation.FloatMemberValue;
    import javassist.bytecode.annotation.IntegerMemberValue;
    import javassist.bytecode.annotation.LongMemberValue;
    import javassist.bytecode.annotation.MemberValueVisitor;
    import javassist.bytecode.annotation.ShortMemberValue;
    import javassist.bytecode.annotation.StringMemberValue;
    import lombok.extern.slf4j.Slf4j;
     * javassist工具类
    @Slf4j
    public class JavassistUtil {
    	private static volatile ClassPool classPool;
    	/**通过追加当前类加载器,解决springboot项目打包之后javassist提示找不到类的问题
    	 * @return
    	public static ClassPool getClassPool() {
    		if(classPool!=null) {
    			return classPool;
    		synchronized (JavassistUtil.class) {
    			if(classPool==null) {
    				ClassPool cp = ClassPool.getDefault();
    				cp.appendClassPath(new LoaderClassPath(JavassistUtil.class.getClassLoader()));
    				classPool = cp;
    		return classPool;
    	/**获取属性类型
    	 * @param propetyType
    	 * @return
    	public static CtClass getFieldType(Class<?> propetyType)  {
            CtClass fieldType= null;
            try{
                if (!propetyType.isAssignableFrom(Void.class)){
                    fieldType=classPool.get(propetyType.getName());
                }else{
                    fieldType=classPool.get(String.class.getName());
            }catch (NotFoundException e){
                //抛异常
                ClassClassPath path=new ClassClassPath(propetyType);
                classPool.insertClassPath(path);
                try {
                    fieldType=classPool.get(propetyType.getName());
                } catch (NotFoundException e1) {
                	 log.error(e1.getMessage(),e1);
                    //can't find
            return fieldType;
    	/**获取属性的指定注解
    	 * @param originClass
    	 * @param fieldName
    	 * @param annType
    	 * @return
    	 * @throws Exception
    	public static Annotation getFieldAnnotation(Class<?> originClass, String fieldName, Class<? extends java.lang.annotation.Annotation> annType) throws Exception {
    		CtClass sourceCtClass = JavassistUtil.getClassPool().get(originClass.getName());
    		CtField oriCf = sourceCtClass.getField(fieldName);
    		FieldInfo oriFi = oriCf.getFieldInfo();
    		AnnotationsAttribute annAttr = (AnnotationsAttribute) oriFi.getAttribute(AnnotationsAttribute.visibleTag);
    		return annAttr.getAnnotation(annType.getName());
    	/**获取类的指定注解
    	 * @param originClass
    	 * @param annType
    	 * @return
    	 * @throws Exception
    	public static Annotation getClassAnnotation(Class<?> originClass, Class<? extends java.lang.annotation.Annotation> annType) throws Exception {
    		CtClass sourceCtClass = JavassistUtil.getClassPool().get(originClass.getName());
    		ClassFile classFile = sourceCtClass.getClassFile();
    		AnnotationsAttribute annAttr = (AnnotationsAttribute) classFile.getAttribute(AnnotationsAttribute.visibleTag);
    		return annAttr.getAnnotation(annType.getName());
    	/**向目标类中插入属性
    	 * @param fieldName	属性名称
    	 * @param fieldType	属性类型
    	 * @param targetCtClass	目标类
    	 * @param originAnns	原始注解
    	 * @throws Exception
    	public static void addField(String fieldName, Class<?> fieldType, CtClass targetCtClass, Annotation... originAnns) throws Exception {
    		CtField field = new CtField(getFieldType(fieldType), fieldName, targetCtClass);
    		field.setModifiers(Modifier.PUBLIC);
    		addFieldAnnotation(field, originAnns);
    		targetCtClass.addField(field);
    	/**给属性添加注解
    	 * @param field
    	 * @param originAnns
    	public static void addFieldAnnotation(CtField field, Annotation... originAnns) {
    		if(originAnns!=null && originAnns.length>0) {
    			ConstPool targetCp = field.getFieldInfo().getConstPool();
    			AnnotationsAttribute attr = new AnnotationsAttribute(targetCp, AnnotationsAttribute.visibleTag);
    			for(Annotation originAnn : originAnns) {
    				attr.addAnnotation(copyAnnotation(originAnn, targetCp));
    			field.getFieldInfo().addAttribute(attr);
    	/** 向类添加注解
    	 * @param targetCtClass
    	 * @param modifier
    	 * @param originAnns
    	public static void addClassAnnotation(CtClass targetCtClass, Consumer<Annotation> modifier, Annotation... originAnns) {
    		if(originAnns==null || originAnns.length==0) {
    			return;
    		AnnotationsAttribute attr = new AnnotationsAttribute(targetCtClass.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag);
    		for (Annotation oriAnnotation : originAnns) {
    			Annotation copyAnnotation = JavassistUtil.copyAnnotation(oriAnnotation, targetCtClass);
    			modifier.accept(copyAnnotation);
    			attr.addAnnotation(copyAnnotation);
    		targetCtClass.getClassFile().addAttribute(attr);
    	/**复制注解信息
    	 * @param originAnn
    	 * @param targetCtClass
    	 * @return
    	public static Annotation copyAnnotation(Annotation originAnn, CtClass targetCtClass) {
    		ConstPool targetCp = targetCtClass.getClassFile().getConstPool();
    		return copyAnnotation(originAnn, targetCp);
    	/**复制注解信息
    	 * @param originAnn
    	 * @param targetCp
    	 * @return
    	public static Annotation copyAnnotation(Annotation originAnn, ConstPool targetCp) {
    		Annotation ann = new Annotation(originAnn.getTypeName(), targetCp);
    		Set<String> memberNames = originAnn.getMemberNames();
    		for (String memberName : memberNames) {
    			originAnn.getMemberValue(memberName).accept(getVisitor(targetCp, ann, memberName));
    		return ann;
    	private static MemberValueVisitor getVisitor(ConstPool cp, Annotation ann, String memberName) {
    		return new MemberValueVisitor() {
    			@Override
    			public void visitStringMemberValue(StringMemberValue node) {
    				ann.addMemberValue(memberName, new StringMemberValue(node.getValue(), cp));
    			@Override
    			public void visitShortMemberValue(ShortMemberValue node) {
    				ann.addMemberValue(memberName, new ShortMemberValue(node.getValue(), cp));
    			@Override
    			public void visitLongMemberValue(LongMemberValue node) {
    				ann.addMemberValue(memberName, new LongMemberValue(node.getValue(), cp));
    			@Override
    			public void visitIntegerMemberValue(IntegerMemberValue node) {
    				ann.addMemberValue(memberName, new IntegerMemberValue(cp, node.getValue()));
    			@Override
    			public void visitFloatMemberValue(FloatMemberValue node) {
    				ann.addMemberValue(memberName, new FloatMemberValue(node.getValue(), cp));
    			@Override
    			public void visitEnumMemberValue(EnumMemberValue node) {
    				EnumMemberValue emv = new EnumMemberValue(cp);
    				emv.setType(node.getType());
    				emv.setValue(node.getValue());
    				ann.addMemberValue(memberName, emv);
    			@Override
    			public void visitDoubleMemberValue(DoubleMemberValue node) {
    				ann.addMemberValue(memberName, new DoubleMemberValue(node.getValue(), cp));
    			@Override
    			public void visitClassMemberValue(ClassMemberValue node) {
    				ann.addMemberValue(memberName, new ClassMemberValue(node.getValue(), cp));
    			@Override
    			public void visitCharMemberValue(CharMemberValue node) {
    				ann.addMemberValue(memberName, new CharMemberValue(node.getValue(), cp));
    			@Override
    			public void visitByteMemberValue(ByteMemberValue node) {
    				ann.addMemberValue(memberName, new ByteMemberValue(node.getValue(), cp));
    			@Override
    			public
    
    
    
    
        
     void visitBooleanMemberValue(BooleanMemberValue node) {
    				ann.addMemberValue(memberName, new BooleanMemberValue(node.getValue(), cp));
    			@Override
    			public void visitArrayMemberValue(ArrayMemberValue node) {
    				ArrayMemberValue amv = new ArrayMemberValue(cp);
    				amv.setValue(node.getValue());
    				ann.addMemberValue(memberName, amv);
    			@Override
    			public void visitAnnotationMemberValue(AnnotationMemberValue node) {
    				ann.addMemberValue(memberName, new AnnotationMemberValue(node.getValue(), cp));
    

    展示多种响应结果

    统一响应类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class CommonResult<T> {
        private Integer code;
        private String msg;
        private T data;
    

    替换文档响应结果的类型

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Component;
    import com.fasterxml.classmate.ResolvedType;
    import com.fasterxml.classmate.TypeResolver;
    import com.github.xiaoymin.knife4j.spring.util.ByteUtils;
    import cn.hutool.core.collection.CollUtil;
    import io.swagger.v3.oas.annotations.media.Content;
    import io.swagger.v3.oas.annotations.media.Schema;
    import io.swagger.v3.oas.annotations.responses.ApiResponse;
    import io.swagger.v3.oas.annotations.responses.ApiResponses;
    import springfox.documentation.builders.RepresentationBuilder;
    import springfox.documentation.builders.ResponseBuilder;
    import springfox.documentation.schema.ModelSpecification;
    import springfox.documentation.schema.ModelSpecificationProvider;
    import springfox.documentation.schema.ResolvedTypes;
    import springfox.documentation.schema.TypeNameExtractor;
    import springfox.documentation.schema.plugins.SchemaPluginsManager;
    import springfox.documentation.schema.property.PackageNames;
    import springfox.documentation.service.Response;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.schema.ViewProviderPlugin;
    import springfox.documentation.spi.schema.contexts.ModelContext;
    import springfox.documentation.spi.service.OperationBuilderPlugin;
    import springfox.documentation.spi.service.contexts.OperationContext;
    import springfox.documentation.swagger.common.SwaggerPluginSupport;
    @ConditionalOnProperty(prefix = "swagger", name = "enable", havingValue = "true")
    @Component
    @Order(SwaggerPluginSupport.OAS_PLUGIN_ORDER+1)
    public class ApiResponseSchemaModelReader implements OperationBuilderPlugin {
    	private final TypeNameExtractor typeNameExtractor;
    	private final SchemaPluginsManager pluginsManager;
    	private final ModelSpecificationProvider modelProvider;
    	@Autowired
    	private TypeResolver typeResolver;
    	@Autowired
    	public ApiResponseSchemaModelReader(TypeNameExtractor typeNameExtractor, SchemaPluginsManager pluginsManager,
    			@Qualifier("cachedModels") ModelSpecificationProvider modelProvider) {
    		super();
    		this.typeNameExtractor = typeNameExtractor;
    		this.pluginsManager = pluginsManager;
    		this.modelProvider = modelProvider;
    	@Override
    	public boolean supports(DocumentationType delimiter) {
    		return true;
    	@Override
    	public void apply(OperationContext operationContext) {
    		if(applyV3(operationContext)) {
    			return;
    		applyV2(operationContext);
             * 解析v3版本注解中的响应类
    	private boolean applyV3(OperationContext context) {
    		Optional<ApiResponses> apiResponses = context.findAnnotation(ApiResponses.class);
    		List<ApiResponse> list = new ArrayList<>();
    		if (apiResponses.isPresent()) {
    			CollUtil.addAll(list, apiResponses.get().value());
    		} else {
    			Optional<ApiResponse> apiResponse = context.findAnnotation(ApiResponse.class);
    			if (apiResponse.isPresent()) {
    				list.add(apiResponse.get());
    		if (list.isEmpty()) {
    			return false;
    		for (ApiResponse apiResponse : list) {
    			replaceResponse(context, apiResponse);
    		return list.size()>0;
             * 解析v2版本注解中的响应类
    	private boolean applyV2(OperationContext context) {
    		Optional<io.swagger.annotations.ApiResponses> apiResponses = context.findAnnotation(io.swagger.annotations.ApiResponses.class);
    		List<io.swagger.annotations.ApiResponse> list = new ArrayList<>();
    		if (apiResponses.isPresent()) {
    			CollUtil.addAll(list, apiResponses.get().value());
    		} else {
    			Optional<io.swagger.annotations.ApiResponse> apiResponse = context.findAnnotation(io.swagger.annotations.ApiResponse.class);
    			if (apiResponse.isPresent()) {
    				list.add(apiResponse.get());
    		if (list.isEmpty()) {
    			return false;
    		for (io.swagger.annotations.ApiResponse apiResponse : list) {
    			Class<?> clazz = apiResponse.response();
    			replace(context, clazz, String.valueOf(apiResponse.code()), apiResponse.message());
    		return list.size()>0;
    	private void replaceResponse(OperationContext operationContext, ApiResponse apiResponse) {
    		Content[] contents = apiResponse.content();
    		if (contents == null || contents.length == 0) {
    			return;
    		for (Content content : contents) {
    			Schema schema = content.schema();
    			Class<?> target = schema.implementation();
    			replace(operationContext, target, apiResponse.responseCode(), apiResponse.description());
    	private void replace(OperationContext operationContext, Class<?> target, String code, String description) {
    		if (target == Void.class || target == null || CommonResult.class.isAssignableFrom(target)) {
    			return;
    		ResponseBuilder responseBuilder = new ResponseBuilder();
                    //动态生成的类名,须保证唯一性,避免被覆盖
    		String clazzName = CommonResult.class.getName() + target.getSimpleName();
                    // 利用knife4j中的工具类动态创建新的Class
    		Class<?> loadClass = ByteUtils.load(clazzName);
    		if (loadClass == null) {
    			return;
    		ResolvedType returnType = operationContext.alternateFor(typeResolver.resolve(loadClass));
    		if (ResolvedTypes.isVoid(returnType)) {
    			return;
    		ModelContext modelContext = modelContext(operationContext, returnType);
    		Optional<ModelSpecification> modelSpecificationsFor = modelProvider.modelSpecificationsFor(modelContext);
                    // 创建api文档响应结果对应的Model
    		modelSpecificationsFor.ifPresent((mds -> {
    			String typeName = typeNameExtractor.typeName(ModelContext.fromParent(modelContext, returnType));
    			Function<Consumer<RepresentationBuilder>, ResponseBuilder> function = responseBuilder.representation(MediaType.ALL);
    			function.apply(builder -> {
    				builder.model(mdsBuilder -> {
    					mdsBuilder.name(mds.getName()).referenceModel(rm->{
    						rm.key(m -> m.qualifiedModelName(q -> q.namespace(PackageNames.safeGetPackageName(returnType)).name(typeName))
    								.viewDiscriminator(modelContext.getView().orElse(null))
    								.validationGroupDiscriminators(modelContext.getValidationGroups()).isResponse(modelContext.isReturnType()).build());
    			Response response = responseBuilder.code(code).description(description).build();
    			operationContext.operationBuilder().responses(Arrays.asList(response));
    	private ModelContext modelContext(OperationContext operationContext, ResolvedType returnType) {
    		ViewProviderPlugin viewProvider = pluginsManager.viewProvider(operationContext.getDocumentationContext().getDocumentationType());
    		return operationContext.operationModelsBuilder().addReturn(returnType, viewProvider.viewFor(operationContext));
    

    替换文档响应结果中的model

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.Set;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import com.fasterxml.classmate.ResolvedType;
    import com.fasterxml.classmate.TypeResolver;
    import cn.hutool.core.collection.CollUtil;
    import io.swagger.annotations.ApiModelProperty;
    import io.swagger.v3.oas.annotations.media.Content;
    import io.swagger.v3.oas.annotations.media.Schema;
    import io.swagger.v3.oas.annotations.responses.ApiResponse;
    import io.swagger.v3.oas.annotations.responses.ApiResponses;
    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.ConstPool;
    import javassist.bytecode.annotation.Annotation;
    import javassist.bytecode.annotation.BooleanMemberValue;
    import javassist.bytecode.annotation.StringMemberValue;
    import lombok.extern.slf4j.Slf4j;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.OperationModelsProviderPlugin;
    import springfox.documentation.spi.service.contexts.RequestMappingContext;
    @Slf4j
    @ConditionalOnProperty(prefix = "swagger", name = "enable", havingValue = "true")
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE+14)
    public class ApiResponseSchemaModelsProvider implements OperationModelsProviderPlugin {
    	@Autowired
        private TypeResolver typeResolver;
    	private Map<Class<?>, ResolvedType> cache = new HashMap<>();
    	@Override
    	public boolean supports(DocumentationType delimiter) {
    		return true;
    	@Override
    	public void apply(RequestMappingContext context) {
    		Set<Class<?>> modelClazzes = findV3SchemaModels(context);
    		if(CollUtil.isEmpty(modelClazzes)) {
    			modelClazzes = findSchemaModels(context);
    		for (Class<?> clazz : modelClazzes) {
    			if
    
    
    
    
        
    (clazz==Void.class || clazz==null || CommonResult.class.isAssignableFrom(clazz)) {
    				continue;
    			ResolvedType modelType = cache.get(clazz);
    			if(modelType==null) {
    				Class<?> dynamicModelClass = createDynamicModelClass(clazz);
    				modelType=context.alternateFor(typeResolver.resolve(dynamicModelClass==null ?clazz : dynamicModelClass));
    				cache.put(clazz, modelType);
                            context.operationModelsBuilder().addReturn(modelType);
    	private Set<Class<?>> findV3SchemaModels(RequestMappingContext context) {
    		Optional<ApiResponses> apiResponses = context.findAnnotation(ApiResponses.class);
    		List<ApiResponse> list = new ArrayList<>();
    		if(apiResponses.isPresent()) {
    			CollUtil.addAll(list, apiResponses.get().value());
    		}else {
    			Optional<ApiResponse> apiResponse = context.findAnnotation(ApiResponse.class);
    			if(apiResponse.isPresent()) {
    				list.add(apiResponse.get());
    		if(list.isEmpty()) {
    			return Collections.emptySet();
    		Set<Class<?>> modelClazzes = new LinkedHashSet<>();
    		for (ApiResponse apiResponse : list) {
    			Content[] contents = apiResponse.content();
    			if(contents==null || contents.length==0) {
    				continue;
    			for (Content content : contents) {
    				Schema schema = content.schema();
    				modelClazzes.add(schema.implementation());
    		return modelClazzes;
    	private Set<Class<?>> findSchemaModels(RequestMappingContext context) {
    		Optional<io.swagger.annotations.ApiResponses> apiResponses = context.findAnnotation(io.swagger.annotations.ApiResponses.class);
    		List<io.swagger.annotations.ApiResponse> list = new ArrayList<>();
    		if(apiResponses.isPresent()) {
    			CollUtil.addAll(list, apiResponses.get().value());
    		}else {
    			Optional<io.swagger.annotations.ApiResponse> apiResponse = context.findAnnotation(io.swagger.annotations.ApiResponse.class);
    			if(apiResponse.isPresent()) {
    				list.add(apiResponse.get());
    		if(list.isEmpty()) {
    			return Collections.emptySet();
    		Set<Class<?>> modelClazzes = new LinkedHashSet<>();
    		for (io.swagger.annotations.ApiResponse apiResponse : list) {
    			Class<?> clazz = apiResponse.response();
    			modelClazzes.add(clazz);
    		return modelClazzes;
         * createModel
         * @param name 类名
         * @param parameters 属性集合
         * @return 动态生成类
        public static Class<?> createDynamicModelClass(Class<?> target){
            String clazzName= CommonResult.class.getName() + target.getSimpleName();
            ClassPool classPool = JavassistUtil.getClassPool();
            try {
                CtClass tmp=classPool.getCtClass(clazzName);
                if (tmp!=null){
                    tmp.detach();
            } catch (NotFoundException e) {
            CtClass ctClass=classPool.makeClass(clazzName);
            try{
            	ctClass.addField(createField("code", int.class, "响应码", ctClass));
            	ctClass.addField(createField("msg", String.class, "响应消息", ctClass));
            	ctClass.addField(createField("data", target, "数据", ctClass));
            	return ctClass.toClass();
            }catch (Throwable e){
                log.error(e.getMessage());
            return null;
        private static CtField createField(String name, Class<?> type, String remark, CtClass ctClass) throws NotFoundException, CannotCompileException {
            CtField field=new CtField(JavassistUtil.getFieldType(type),name,ctClass);
            field.setModifiers(Modifier.PUBLIC);
            ConstPool constPool=ctClass.getClassFile().getConstPool();
            AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
            Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
            ann.addMemberValue("value", new StringMemberValue(remark, constPool));
            ann.addMemberValue("required", new BooleanMemberValue(false,constPool));
            attr.addAnnotation(ann);
            field.getFieldInfo().addAttribute(attr);
            return field;
    

    隐藏输入参数

    自定义注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     * 忽略某些请求参数
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ApiIgnoreInParam {
    	/** 指定的类型
    	 * @return
    	Class<?> value() default Void.class;
    	/** 指定的类型的属性名称
    	 * @return
    	String[] names();
    	@Target(ElementType.METHOD)
    	@Retention(RetentionPolicy.RUNTIME)
    	@Documented
    	public @interface ApiIgnoreInParams {
    		ApiIgnoreInParam[] value();
    

    替换请求参数类

    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.atomic.AtomicInteger;
    import org.apache.commons.collections4.keyvalue.MultiKey;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import com.fasterxml.classmate.ResolvedType;
    import com.fasterxml.classmate.TypeResolver;
    import cn.hutool.core.text.CharSequenceUtil;
    import cn.hutool.core.util.ReflectUtil;
    import cn.hutool.json.JSONUtil;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import javassist.CtClass;
    import javassist.bytecode.annotation.Annotation;
    import javassist.bytecode.annotation.StringMemberValue;
    import lombok.extern.slf4j.Slf4j;
    import springfox.documentation.service.ResolvedMethodParameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.OperationModelsProviderPlugin;
    import springfox.documentation.spi.service.contexts.RequestMappingContext;
    @Slf4j
    @ConditionalOnProperty(prefix = "swagger", name = "enable", havingValue = "true")
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE + 15)
    public class ApiIgnoreSchemaModelsProvider implements OperationModelsProviderPlugin {
    	private static Map<MultiKey<String>, ResolvedType> cache = new HashMap<>();
    	private static AtomicInteger num = new AtomicInteger();
    	@Autowired
    	private TypeResolver typeResolver;
    	static ResolvedType getCachedResolvedType(Class<?> rawType, Set<String> names) {
    		List<String> sortedNames = new ArrayList<>(names);
    		Collections.sort(sortedNames);
    		MultiKey<String> key = new MultiKey<String>(rawType.getName(), JSONUtil.toJsonStr(sortedNames));
    		return cache.get(key);
    	@Override
    	public boolean supports(DocumentationType delimiter) {
    		return true;
    	@Override
    	public void apply(RequestMappingContext context) {
    		Map<Class, Set<String>> ignores = getIgnoreFieldNames(context);
    		if (ignores.isEmpty()) {
    			return;
    		List<ResolvedMethodParameter> parameters = context.getParameters();
    		for (ResolvedMethodParameter methodParameter : parameters) {
    			Class<?> originClass = methodParameter.getParameterType().getErasedType();
    			Set<String> nestedNames = ignores.get(originClass);
    			if (nestedNames == null) {
    				return;
    			List<String> sortedNames = new ArrayList<>(nestedNames);
    			Collections.sort(sortedNames);
    			MultiKey<String> key = new MultiKey<String>(originClass.getName(), JSONUtil.toJsonStr(sortedNames));
    			if (cache.containsKey(key)) {
    				continue;
    			// 忽略指定的属性
    			Field[] fields = ReflectUtil.getFields(originClass, f -> f.isAnnotationPresent(ApiModelProperty.class) && !nestedNames.contains(f.getName()));
    			if (fields == null || fields.length == 0) {
    				return;
    			Set<Class> processed = new HashSet<>();
    			Map<Class, List<Field>> validFieldMap = new HashMap<>();
    			filterField(originClass, ignores, validFieldMap, processed);
    			// 创建忽略指定属性的Model类
    			String targetClassName = getDynClassName(originClass);
    			Class<?> modelClass = createModelClass(targetClassName, originClass, validFieldMap);
    			if (modelClass == null) {
    				return;
    			ResolvedType resolvedType = context.alternateFor(typeResolver.resolve(modelClass));
    			context.operationModelsBuilder().addInputParam(resolvedType);
    			cache.put(key, resolvedType);
    	private Class<?> createModelClass(String targetClassName, Class<?> originClass, Map<Class, List<Field>> validFieldMap) {
    		List<Field> fields = validFieldMap.get(originClass);
    		if (fields == null) {
    			return null;
    		try {
    			CtClass targetCtClass = JavassistUtil.getClassPool().makeClass(targetClassName);
    			// 为动态创建的类添加@ApiModel注解
    			Annotation oriAnnotation = JavassistUtil.getClassAnnotation(originClass, ApiModel.class);
    			JavassistUtil.addClassAnnotation(targetCtClass, ca->{
    				// 使用唯一名称覆盖原来的Model名称,避免引用到原始Model
    				StringMemberValue smv = (StringMemberValue) ca.getMemberValue("value");
    				smv.setValue(smv.getValue() + "_" + num.incrementAndGet());
    			}, oriAnnotation);
    			// 添加类属性
    			for (Field field : fields) {
    				Class<?> ft = field.getType();
    				if (validFieldMap.containsKey(ft)) {
    					ft = createModelClass(getDynClassName(ft), ft, validFieldMap);
    					if (ft == null) {
    						continue;
    				Annotation originAnn = JavassistUtil.getFieldAnnotation(originClass, field.getName(), ApiModelProperty.class);
    				JavassistUtil.addField(field.getName(), ft, targetCtClass, originAnn);
    			return targetCtClass.toClass();
    		} catch (Exception e) {
    			log.error("动态隐藏接口参数出错", e);
    			return null;
    	private String getDynClassName(Class<?> clazz) {
    		return clazz.getName() + "_" + UUID.randomUUID().toString().replace('-', '_');
    	private void filterField(Class<?> originClass, Map<Class, Set<String>> ignores, Map<Class, List<Field>> validFieldMap, Set<Class> processed)
    
    
    
    
        
     {
    		if (!processed.add(originClass)) {
    			return;
    		Field[] fields = ReflectUtil.getFields(originClass, f -> f.isAnnotationPresent(ApiModelProperty.class));
    		if (fields == null || fields.length == 0) {
    			return;
    		List<Field> validFields = new ArrayList<>(fields.length);
    		validFieldMap.put(originClass, validFields);
    		Set<String> names = ignores.get(originClass);
    		for (Field field : fields) {
    			if (names != null && names.contains(field.getName())) {
    				continue;
    			validFields.add(field);
    			Class<?> ft = field.getType();
    			if (!ft.isAnnotationPresent(ApiModel.class)) {
    				continue;
    			filterField(ft, ignores, validFieldMap, processed);
    	private Map<Class, Set<String>> getIgnoreFieldNames(RequestMappingContext context) {
    		Optional<ApiIgnoreInParams> more = context.findAnnotation(ApiIgnoreInParams.class);
    		List<ApiIgnoreInParam> inParams = new ArrayList<>();
    		more.ifPresent(ps -> Arrays.stream(ps.value()).forEach(inParams::add));
    		Optional<ApiIgnoreInParam> one = context.findAnnotation(ApiIgnoreInParam.class);
    		one.ifPresent(inParams::add);
    		Map<Class, Set<String>> map = new HashMap<>(inParams.size(), 1);
    		for (ApiIgnoreInParam apiIgnoreInParam : inParams) {
    			Set<String> names = map.get(apiIgnoreInParam.value());
    			if (names == null) {
    				names = new HashSet<>();
    				map.put(apiIgnoreInParam.value(), names);
    			for (String name : apiIgnoreInParam.names()) {
    				if (CharSequenceUtil.isNotBlank(name)) {
    					names.add(name);
    			if (!names.isEmpty()) {
    				map.put(apiIgnoreInParam.value(), names);
    		return map;
    

    替换文档中的请求参数Model

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.Set;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import com.fasterxml.classmate.ResolvedType;
    import cn.hutool.core.text.CharSequenceUtil;
    import javassist.ClassPool;
    import springfox.documentation.schema.ModelSpecification;
    import springfox.documentation.schema.ModelSpecificationProvider;
    import springfox.documentation.schema.plugins.SchemaPluginsManager;
    import springfox.documentation.schema.property.ModelSpecificationFactory;
    import springfox.documentation.service.ResolvedMethodParameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.schema.ViewProviderPlugin;
    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;
    @SuppressWarnings("deprecation")
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE + 101)
    public class ApiIgnoreParameterBuilderPlugin implements ParameterBuilderPlugin {
    	static final ClassPool classPool = ClassPool.getDefault();
    	@Autowired
    	private SchemaPluginsManager pluginsManager;
    	@Autowired
    	private ModelSpecificationFactory models;
    	@Autowired
    	@Qualifier("cachedModels")
    	private ModelSpecificationProvider modelProvider;
    	@Override
    	public boolean supports(DocumentationType delimiter) {
    		return true;
    	@Override
    	public void apply(ParameterContext parameterContext) {
    		Map<Class, Set<String>> ignores = getIgnoreFieldNames(parameterContext);
    		if (ignores.isEmpty()) {
    			return;
    		ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
    		Optional<String> defaultName = methodParameter.defaultName();
    		Set<String> names = ignores.get(Void.class);
    		// 未明确指定类型,则当作全局数据处理
    		if (names != null && defaultName.isPresent() && names.contains(defaultName.get())) {
    			parameterContext.parameterBuilder().hidden(true);
    			return;
    		Class<?> originClass = methodParameter.getParameterType().getErasedType();
    		Set<String> nestedNames = ignores.get(originClass);
    		if (nestedNames == null) {
    			return;
    		ResolvedType resolvedType = ApiIgnoreSchemaModelsProvider.getCachedResolvedType(originClass, nestedNames);
    		if(resolvedType==null) {
    			return;
                    // 替换参数对应的Model
    		ResolvedType parameterType = parameterContext.alternateFor(resolvedType);
    		ModelContext modelContext = modelContext(parameterContext, methodParameter, parameterType);
    		ModelSpecification parameterModel = models.create(modelContext, parameterType);
    		parameterContext.requestParameterBuilder().contentModel(parameterModel);
    	private Map<Class, Set<String>> getIgnoreFieldNames(ParameterContext parameterContext) {
    		OperationContext operationContext = parameterContext.getOperationContext();
    		Optional<ApiIgnoreInParams> more = operationContext.findAnnotation(ApiIgnoreInParams.class);
    		List<ApiIgnoreInParam> inParams = new ArrayList<>();
    		more.ifPresent(ps -> Arrays.stream(ps.value()).forEach(inParams::add));
    		Optional<ApiIgnoreInParam> one = operationContext.findAnnotation(ApiIgnoreInParam.class);
    		one.ifPresent(inParams::add);
    		Map<Class, Set<String>> map = new HashMap<>(inParams.size(), 1);
    		for (ApiIgnoreInParam apiIgnoreInParam : inParams) {
    			Set<String> names = map.get(apiIgnoreInParam.value());
    			if (names == null) {
    				names = new HashSet<>();
    				map.put(apiIgnoreInParam.value(), names);
    			for (String name : apiIgnoreInParam.names()) {
    				if (CharSequenceUtil.isNotBlank(name)) {
    					names.add(name);
    			if (!names.isEmpty()) {
    				map.put(apiIgnoreInParam.value(), names);
    		return map;
    	private ModelContext modelContext(ParameterContext context, ResolvedMethodParameter methodParameter, ResolvedType parameterType) {
    		ViewProviderPlugin viewProvider = pluginsManager.viewProvider(context.getDocumentationContext().getDocumentationType());
    		return context.getOperationContext().operationModelsBuilder().addInputParam(parameterType, viewProvider.viewFor(methodParameter), new HashSet<>());
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import io.swagger.annotations.Api;
    import io.swagger.v3.oas.annotations.Operation;
    import io.swagger.v3.oas.annotations.media.Content;
    import io.swagger.v3.oas.annotations.media.Schema;
    import io.swagger.v3.oas.annotations.responses.ApiResponse;
    import io.swagger.v3.oas.annotations.responses.ApiResponses;
    @Api(value = "演示接口", tags = "演示接口")
    @RestController
    @RequestMapping("/demo")
    public class DemoController {
    	@Operation(summary = "多种类型响应结果", description = "多种类型响应结果")
    	@ApiResponses({
    		@ApiResponse(responseCode = "200-1", description = "响应结果详情1", content = @Content(schema = @Schema(implementation = ResponseResult1.class)))
    		,@ApiResponse(responseCode = "200-2", description = "响应结果详情2", content = @Content(schema = @Schema(implementation = ResponseResult2.class)))
    	@GetMapping("/multiTypeResponse")
    	public CommonResult<Object> multiTypeResponse() {
    		return CommonResult.success(null);
            //若要隐藏请求参数中某个类型的某些属性,那么需使用@ApiIgnoreInParams
            //@ApiIgnoreInParams({
            //@ApiIgnoreInParam(value = RequestParams.class, names = {"param1"}),
            //@ApiIgnoreInParam(value = Nested.class, names = {"field1"}),
            //上面的用法,将会隐藏RequestParams类型的param1属性,以及Nested类型的field1属性
            //不区分RequestParams嵌套Nested,还是Nested嵌套RequestParams
    	@ApiIgnoreInParam(value = RequestParams.class, names = {"param1", "param2", "param3"})
    	@Operation(summary = "隐藏指定参数", description = "隐藏指定参数")
    	@PostMapping("/hideRequestParams")
    	public CommonResult<Boolean> hideRequestParams(@RequestBody RequestParams param) {
    		return CommonResult.success(true);
    
    @ApiModel("响应结果1")
    @Getter
    @Setter
    public class ResponseResult1 {
    	@ApiModelProperty("id")
    	private String id;
    	@ApiModelProperty("名称")
    	private String name;
    
    @ApiModel("响应结果2")
    @Getter
    @Setter
    public class ResponseResult2 {
    	@ApiModelProperty("姓名")
    	private String name;
    	@ApiModelProperty("年龄")
    	private int age;
    
    @ApiModel("请求参数")
    @Getter
    @Setter
    public class RequestParams {
    	@ApiModelProperty("参数1")
    	private String param1;
    	@ApiModelProperty("参数2")
    	private String param2;
    	@ApiModelProperty("参数3")
    	private String param3;
    	@ApiModelProperty("姓名")
    	private String name;
    	@ApiModelProperty("地址")
    	private String address;
    复制代码
    分类:
    后端
    标签: