美文网首页
knif4j (springfox-spring-web)文档生

knif4j (springfox-spring-web)文档生

作者: 一个忙来无聊的人 | 来源:发表于2020-09-03 10:06 被阅读0次

问题描述 knif4j 集成后启动时文档无法识别内容导致启动较慢或者长时间启动不了,且访问异常。

Scanning for api listing references
日志打印信息如下

2020-09-02 17:58:45.545  INFO 25928 --- [           main] l.lockservice.StandardLockService        : Successfully released change log lock
2020-09-02 17:58:45.944  INFO 25928 --- [           main] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String,javax.servlet.http.HttpServletRequest)]
2020-09-02 17:58:46.874  INFO 25928 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2020-09-02 17:58:46.891  INFO 25928 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2020-09-02 17:58:46.961  INFO 25928 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
2020-09-02 17:58:55.006  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: saveUsingPOST_1
2020-09-02 17:58:55.020  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: deleteUsingDELETE_1
2020-09-02 17:58:55.353  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: listUsingGET_1
2020-09-02 17:58:55.681  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: queryByIdUsingGET_1
2020-09-02 17:58:56.020  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: saveUsingPOST_2
2020-09-02 17:58:56.357  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: listUsingGET_2
2020-09-02 17:58:56.694  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: queryByIdUsingGET_2
2020-09-02 17:59:11.686  INFO 25928 --- [           main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: deleteUsingDELETE_2

java 代码样例说明

  • 抽象类、基类:
    每一个实体类的父类。重点关注 protected User currentUser; 这是一个对象
public abstract class BaseEntity<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = " 主键")
    protected String id;

    @ApiModelProperty(value = " 当前用户")
    protected User currentUser;

  • 用户实体类
    该类继承了baseEntity, 但是同时有 User 对象, User createBy; User updateBy;
public abstract class DataEntity<T> extends BaseEntity<T> {

    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = " 备注")
    protected String remarks;
    @ApiModelProperty(value = " 创建者")
    protected User createBy;
    @ApiModelProperty(value = " 创建日期")
    protected Date createDate;
    @ApiModelProperty(value = " 更新者")
    protected User updateBy;
  • 具体用户实体类
    可以发现继承
@Data
public class User extends DataEntity<User> {
    private static final long serialVersionUID = 1L;
    private String loginName;// 登录名
    private String password;// 密码
    private String no;      // 工号
    private String name;    // 姓名
    private String email;   // 邮箱
    private String phone;   // 电话
    private String mobile;  // 手机
  • 其他类
@Data
public class DictType extends DataEntity<DictType> {
    
    private static final long serialVersionUID = 1L;
    private String type;        // 类型
    private String description; 

因为一些其他的逻辑类,导致在使用时 User 对象为空,循环去获取……

最后贴一下解决办法

  • 自定义注解 IgnoreSwaggerParameter
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerParameter {
}
  • 在类中添加自定义注解
    此处就不一一添加了。
public abstract class DataEntity<T> extends BaseEntity<T> {

    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = " 备注")
    protected String remarks;
    @ApiModelProperty(value = " 创建者")
@IgnoreSwaggerParameter
    protected User createBy;
    @ApiModelProperty(value = " 创建日期")
    protected Date createDate;
    @ApiModelProperty(value = " 更新者")
@IgnoreSwaggerParameter
    protected User updateBy;
  • 重写 ModelAttributeParameterExpander 类,其他内容都不变,请注意包路径一致,并且新增 @Primary 表示按照当前为准
    重点内容是 重写 propertyDescriptors。过滤
/*
 *
 *  Copyright 2015-2018 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */

package springfox.documentation.spring.web.readers.parameter;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedField;
import com.fasterxml.classmate.members.ResolvedMethod;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.Maps;
import springfox.documentation.schema.Types;
import springfox.documentation.schema.property.bean.AccessorsProvider;
import springfox.documentation.schema.property.field.FieldProvider;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.schema.AlternateTypeProvider;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Objects.*;
import static com.google.common.base.Predicates.*;
import static com.google.common.base.Strings.*;
import static com.google.common.collect.FluentIterable.*;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Sets.*;
import static springfox.documentation.schema.Collections.*;
import static springfox.documentation.schema.Types.*;
import static springfox.documentation.spring.web.readers.parameter.ParameterTypeDeterminer.*;

@Component
@Primary
public class ModelAttributeParameterExpander {
  private static final Logger LOG = LoggerFactory.getLogger(ModelAttributeParameterExpander.class);
  private final FieldProvider fields;
  private final AccessorsProvider accessors;
  private final EnumTypeDeterminer enumTypeDeterminer;

  @Autowired
  protected DocumentationPluginsManager pluginsManager;

  @Autowired
  public ModelAttributeParameterExpander(
      FieldProvider fields,
      AccessorsProvider accessors,
      EnumTypeDeterminer enumTypeDeterminer) {
    this.fields = fields;
    this.accessors = accessors;
    this.enumTypeDeterminer = enumTypeDeterminer;
  }

  public List<Parameter> expand(ExpansionContext context) {
    List<Parameter> parameters = newArrayList();
    Set<PropertyDescriptor> propertyDescriptors = propertyDescriptors(context.getParamType().getErasedType());
    Map<Method, PropertyDescriptor> propertyLookupByGetter
        = propertyDescriptorsByMethod(context.getParamType().getErasedType(), propertyDescriptors);
    Iterable<ResolvedMethod> getters = FluentIterable.from(accessors.in(context.getParamType()))
        .filter(onlyValidGetters(propertyLookupByGetter.keySet()));

    Map<String, ResolvedField> fieldsByName = FluentIterable.from(this.fields.in(context.getParamType()))
        .uniqueIndex(new Function<ResolvedField, String>() {
          @Override
          public String apply(ResolvedField input) {
            return input.getName();
          }
        });


    LOG.debug("Expanding parameter type: {}", context.getParamType());
    final AlternateTypeProvider alternateTypeProvider = context.getDocumentationContext().getAlternateTypeProvider();

    FluentIterable<ModelAttributeField> attributes =
        allModelAttributes(
            propertyLookupByGetter,
            getters,
            fieldsByName,
            alternateTypeProvider);

    FluentIterable<ModelAttributeField> expendables = attributes
        .filter(not(simpleType()))
        .filter(not(recursiveType(context)));
    for (ModelAttributeField each : expendables) {
      LOG.debug("Attempting to expand expandable property: {}", each.getName());
      parameters.addAll(
          expand(
              context.childContext(
                  nestedParentName(context.getParentName(), each),
                  each.getFieldType(),
                  context.getOperationContext())));
    }

    FluentIterable<ModelAttributeField> collectionTypes = attributes
        .filter(and(isCollection(), not(recursiveCollectionItemType(context.getParamType()))));
    for (ModelAttributeField each : collectionTypes) {
      LOG.debug("Attempting to expand collection/array field: {}", each.getName());

      ResolvedType itemType = collectionElementType(each.getFieldType());
      if (Types.isBaseType(itemType) || enumTypeDeterminer.isEnum(itemType.getErasedType())) {
        parameters.add(simpleFields(context.getParentName(), context, each));
      } else {
        ExpansionContext childContext = context.childContext(
            nestedParentName(context.getParentName(), each),
            itemType,
            context.getOperationContext());
        if (!context.hasSeenType(itemType)) {
          parameters.addAll(expand(childContext));
        }
      }
    }

    FluentIterable<ModelAttributeField> simpleFields = attributes.filter(simpleType());
    for (ModelAttributeField each : simpleFields) {
      parameters.add(simpleFields(context.getParentName(), context, each));
    }
    return FluentIterable.from(parameters)
        .filter(not(hiddenParameters()))
        .filter(not(voidParameters()))
        .toList();
  }

  private FluentIterable<ModelAttributeField> allModelAttributes(
      Map<Method, PropertyDescriptor> propertyLookupByGetter,
      Iterable<ResolvedMethod> getters,
      Map<String, ResolvedField> fieldsByName,
      AlternateTypeProvider alternateTypeProvider) {

    FluentIterable<ModelAttributeField> modelAttributesFromGetters = from(getters)
        .transform(toModelAttributeField(fieldsByName, propertyLookupByGetter, alternateTypeProvider));

    FluentIterable<ModelAttributeField> modelAttributesFromFields = from(fieldsByName.values())
        .filter(publicFields())
        .transform(toModelAttributeField(alternateTypeProvider));

    return FluentIterable.from(Sets.union(
        modelAttributesFromFields.toSet(),
        modelAttributesFromGetters.toSet()));
  }

  private Function<ResolvedField, ModelAttributeField> toModelAttributeField(
      final AlternateTypeProvider alternateTypeProvider) {

    return new Function<ResolvedField, ModelAttributeField>() {
      @Override
      public ModelAttributeField apply(ResolvedField input) {
        return new ModelAttributeField(
            alternateTypeProvider.alternateFor(input.getType()),
            input.getName(),
            input,
            input);
      }
    };
  }

  private Predicate<ResolvedField> publicFields() {
    return new Predicate<ResolvedField>() {
      @Override
      public boolean apply(ResolvedField input) {
        return input.isPublic();
      }
    };
  }

  private Predicate<Parameter> voidParameters() {
    return new Predicate<Parameter>() {
      @Override
      public boolean apply(Parameter input) {
        return isVoid(input.getType().orNull());
      }
    };
  }

  private Predicate<ModelAttributeField> recursiveCollectionItemType(final ResolvedType paramType) {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return equal(collectionElementType(input.getFieldType()), paramType);
      }
    };
  }

  private Predicate<Parameter> hiddenParameters() {
    return new Predicate<Parameter>() {
      @Override
      public boolean apply(Parameter input) {
        return input.isHidden();
      }
    };
  }

  private Parameter simpleFields(
      String parentName,
      ExpansionContext context,
      ModelAttributeField each) {
    LOG.debug("Attempting to expand field: {}", each);
    String dataTypeName = Optional.fromNullable(typeNameFor(each.getFieldType().getErasedType()))
        .or(each.getFieldType().getErasedType().getSimpleName());
    LOG.debug("Building parameter for field: {}, with type: ", each, each.getFieldType());
    ParameterExpansionContext parameterExpansionContext = new ParameterExpansionContext(
        dataTypeName,
        parentName,
        determineScalarParameterType(
            context.getOperationContext().consumes(),
            context.getOperationContext().httpMethod()),
        new ModelAttributeParameterMetadataAccessor(
            each.annotatedElements(),
            each.getFieldType(),
            each.getName()),
        context.getDocumentationContext().getDocumentationType(),
        new ParameterBuilder());
    return pluginsManager.expandParameter(parameterExpansionContext);
  }

  private Predicate<ModelAttributeField> recursiveType(final ExpansionContext context) {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return context.hasSeenType(input.getFieldType());
      }
    };
  }

  private Predicate<ModelAttributeField> simpleType() {
    return and(not(isCollection()), not(isMap()),
        or(
            belongsToJavaPackage(),
            isBaseType(),
            isEnum()));
  }

  private Predicate<ModelAttributeField> isCollection() {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return isContainerType(input.getFieldType());
      }
    };
  }

  private Predicate<ModelAttributeField> isMap() {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return Maps.isMapType(input.getFieldType());
      }
    };
  }

  private Predicate<ModelAttributeField> isEnum() {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return enumTypeDeterminer.isEnum(input.getFieldType().getErasedType());
      }
    };
  }

  private Predicate<ModelAttributeField> belongsToJavaPackage() {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return ClassUtils.getPackageName(input.getFieldType().getErasedType()).startsWith("java.lang");
      }
    };
  }

  private Predicate<ModelAttributeField> isBaseType() {
    return new Predicate<ModelAttributeField>() {
      @Override
      public boolean apply(ModelAttributeField input) {
        return Types.isBaseType(input.getFieldType())
            || input.getFieldType().isPrimitive();
      }
    };
  }

  private Function<ResolvedMethod, ModelAttributeField> toModelAttributeField(
      final Map<String, ResolvedField> fieldsByName,
      final Map<Method, PropertyDescriptor> propertyLookupByGetter,
      final AlternateTypeProvider alternateTypeProvider) {
    return new Function<ResolvedMethod, ModelAttributeField>() {
      @Override
      public ModelAttributeField apply(ResolvedMethod input) {
        String name = propertyLookupByGetter.get(input.getRawMember()).getName();
        return new ModelAttributeField(
            fieldType(alternateTypeProvider, input),
            name,
            input,
            fieldsByName.get(name));
      }
    };
  }

  private Predicate<ResolvedMethod> onlyValidGetters(final Set<Method> methods) {
    return new Predicate<ResolvedMethod>() {
      @Override
      public boolean apply(ResolvedMethod input) {
        return methods.contains(input.getRawMember());
      }
    };
  }

  private String nestedParentName(String parentName, ModelAttributeField attribute) {
    String name = attribute.getName();
    ResolvedType fieldType = attribute.getFieldType();
    if (isContainerType(fieldType) && !Types.isBaseType(collectionElementType(fieldType))) {
      name += "[0]";
    }

    if (isNullOrEmpty(parentName)) {
      return name;
    }
    return String.format("%s.%s", parentName, name);
  }

  private ResolvedType fieldType(AlternateTypeProvider alternateTypeProvider, ResolvedMethod method) {
    return alternateTypeProvider.alternateFor(method.getType());
  }

     private Set<PropertyDescriptor> propertyDescriptors(final Class<?> clazz) {
        try {
            Set<PropertyDescriptor> beanProps = new HashSet<>();
            PropertyDescriptor[] descriptors = getBeanInfo(clazz).getPropertyDescriptors();

            for (PropertyDescriptor descriptor : descriptors) {
                Field field = null;
                try {
                    field = ReflectUtil.getField(clazz, descriptor.getName());
                } catch (Exception e) {
                    LOG.debug(String.format("Failed to get bean properties on (%s)", clazz), e);
                }
                if (field != null) {
                    field.setAccessible(true);
                    IgnoreSwaggerParameter ignoreSwaggerParameter = field.getDeclaredAnnotation(IgnoreSwaggerParameter.class);
                    if (ignoreSwaggerParameter != null) {
                        continue;
                    }
                }

                if (descriptor.getReadMethod() != null) {
                    beanProps.add(descriptor);
                }
            }
            return beanProps;
        } catch (Exception e) {
            LOG.warn(String.format("Failed to get bean properties on (%s)", clazz), e);
        }
        return newHashSet();
    }

  private Map<Method, PropertyDescriptor> propertyDescriptorsByMethod(
      final Class<?> clazz,
      Set<PropertyDescriptor> propertyDescriptors) {
    return FluentIterable.from(propertyDescriptors)
        .filter(new Predicate<PropertyDescriptor>() {
          @Override
          public boolean apply(PropertyDescriptor input) {
            return input.getReadMethod() != null
                && !clazz.isAssignableFrom(Collection.class)
                && !"isEmpty".equals(input.getReadMethod().getName());
          }
        })
        .uniqueIndex(new Function<PropertyDescriptor, Method>() {
          @Override
          public Method apply(PropertyDescriptor input) {
            return input.getReadMethod();
          }
        });

  }

  @VisibleForTesting
  BeanInfo getBeanInfo(Class<?> clazz) throws IntrospectionException {
    return Introspector.getBeanInfo(clazz);
  }

}

相关文章

  • knif4j (springfox-spring-web)文档生

    问题描述 knif4j 集成后启动时文档无法识别内容导致启动较慢或者长时间启动不了,且访问异常。 Scanning...

  • Java眼中的XML-文件写入

    DOM 方式生成 XML 文档 通过 SAX 方式生成 XML 文档 通过 DOM4J 方式生成 XML 文档 生...

  • 如何打通文档全生命周期管理的“任督二脉”,让管理效率加倍?

    文档全生命周期是什么? 文档全生命周期,指的是文档从“生”——创建,经“存”——存用,到“终”——销毁的全过程。不...

  • 生啃 uniCloud文档 (一)

    uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编...

  • 微信小程序组件生命周期

    以下内容全部引用微信小程序官方文档,因官方文档内容较多,查找比较繁琐,常用的记录留存。 微信小程序官方文档:组件生...

  • Xcode 自动生产HTML项目文档

    Xcode具有自动生成格式和Apple Developer网站上的API文档几乎一样的HTML项目文档的功能。 生...

  • iOS9.0 集成微信支付

    一、集成微信支付必要SDK和前期的设置工作(参考官方文档) 微信支付官网文档不得不吐槽,这个官方文档难道是实习生写...

  • 编辑权限

    编辑权限 (澳洲)达伦·波克 陈荣生 译 你要修改文档,就需要拥有编辑权限。 它给予你对文档做修改、...

  • LaTex入门体验(二)

    LaTex和HTML很相似,都是用标记语言写好文档,交给特定的编译器渲染,LaTex生成的是pdf文档,HTML生...

  • 用Swagger2markup导出接口文档

    前言 最近公司正好需要整理接口文档,就想把Swagger2的文档导出来。 开始配置 pom.xml 使用单元测试生...

网友评论

      本文标题:knif4j (springfox-spring-web)文档生

      本文链接:https://www.haomeiwen.com/subject/qqiwsktx.html