问题描述 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.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 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 extends BaseEntity {
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 {
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 {
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 extends BaseEntity {
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 expand(ExpansionContext context) {
List parameters = newArrayList();
Set propertyDescriptors = propertyDescriptors(context.getParamType().getErasedType());
Map propertyLookupByGetter
= propertyDescriptorsByMethod(context.getParamType().getErasedType(), propertyDescriptors);
Iterable getters = FluentIterable.from(accessors.in(context.getParamType()))
.filter(onlyValidGetters(propertyLookupByGetter.keySet()));
Map fieldsByName = FluentIterable.from(this.fields.in(context.getParamType()))
.uniqueIndex(new Function() {
@Override
public String apply(ResolvedField input) {
return input.getName();
}
});
LOG.debug("Expanding parameter type: {}", context.getParamType());
final AlternateTypeProvider alternateTypeProvider = context.getDocumentationContext().getAlternateTypeProvider();
FluentIterable attributes =
allModelAttributes(
propertyLookupByGetter,
getters,
fieldsByName,
alternateTypeProvider);
FluentIterable 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 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 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 allModelAttributes(
Map propertyLookupByGetter,
Iterable getters,
Map fieldsByName,
AlternateTypeProvider alternateTypeProvider) {
FluentIterable modelAttributesFromGetters = from(getters)
.transform(toModelAttributeField(fieldsByName, propertyLookupByGetter, alternateTypeProvider));
FluentIterable modelAttributesFromFields = from(fieldsByName.values())
.filter(publicFields())
.transform(toModelAttributeField(alternateTypeProvider));
return FluentIterable.from(Sets.union(
modelAttributesFromFields.toSet(),
modelAttributesFromGetters.toSet()));
}
private Function toModelAttributeField(
final AlternateTypeProvider alternateTypeProvider) {
return new Function() {
@Override
public ModelAttributeField apply(ResolvedField input) {
return new ModelAttributeField(
alternateTypeProvider.alternateFor(input.getType()),
input.getName(),
input,
input);
}
};
}
private Predicate publicFields() {
return new Predicate() {
@Override
public boolean apply(ResolvedField input) {
return input.isPublic();
}
};
}
private Predicate voidParameters() {
return new Predicate() {
@Override
public boolean apply(Parameter input) {
return isVoid(input.getType().orNull());
}
};
}
private Predicate recursiveCollectionItemType(final ResolvedType paramType) {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return equal(collectionElementType(input.getFieldType()), paramType);
}
};
}
private Predicate hiddenParameters() {
return new Predicate() {
@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 recursiveType(final ExpansionContext context) {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return context.hasSeenType(input.getFieldType());
}
};
}
private Predicate simpleType() {
return and(not(isCollection()), not(isMap()),
or(
belongsToJavaPackage(),
isBaseType(),
isEnum()));
}
private Predicate isCollection() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return isContainerType(input.getFieldType());
}
};
}
private Predicate isMap() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return Maps.isMapType(input.getFieldType());
}
};
}
private Predicate isEnum() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return enumTypeDeterminer.isEnum(input.getFieldType().getErasedType());
}
};
}
private Predicate belongsToJavaPackage() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return ClassUtils.getPackageName(input.getFieldType().getErasedType()).startsWith("java.lang");
}
};
}
private Predicate isBaseType() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return Types.isBaseType(input.getFieldType())
|| input.getFieldType().isPrimitive();
}
};
}
private Function toModelAttributeField(
final Map fieldsByName,
final Map propertyLookupByGetter,
final AlternateTypeProvider alternateTypeProvider) {
return new Function() {
@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 onlyValidGetters(final Set methods) {
return new Predicate() {
@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 propertyDescriptors(final Class> clazz) {
try {
Set 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 propertyDescriptorsByMethod(
final Class> clazz,
Set propertyDescriptors) {
return FluentIterable.from(propertyDescriptors)
.filter(new Predicate() {
@Override
public boolean apply(PropertyDescriptor input) {
return input.getReadMethod() != null
&& !clazz.isAssignableFrom(Collection.class)
&& !"isEmpty".equals(input.getReadMethod().getName());
}
})
.uniqueIndex(new Function() {
@Override
public Method apply(PropertyDescriptor input) {
return input.getReadMethod();
}
});
}
@VisibleForTesting
BeanInfo getBeanInfo(Class> clazz) throws IntrospectionException {
return Introspector.getBeanInfo(clazz);
}
}