2019独角兽企业重金招聘Python工程师标准>>>
1、加入springfox依赖
io.springfox
springfox-swagger2
2.6.1
io.springfox
springfox-swagger-ui
2.6.1
2、配置org.springframework.web.accept.ContentNegotiationManagerFactoryBean,否则会报错。确保applicationContext.xml 中 xsi:schemaLocation 的 http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 为3.2以上版本
3、配置DispatcherServlet,DispatcherServlet的拦截连接必须为 / ,或者无法访问springfox-swagger的资源
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
1
DispatcherServlet
/
4、在applicationContext.xml中配置
5、在 applicationContext.xml 配置访问springfox-swagger-ui 的静态资源
6、编写swagger配置类,确保
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.eduboss.utils.PropertiesUtils;
import com.google.common.base.Predicate;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
@EnableWebMvc
@ComponentScan(SwaggerConfig.controllerPackages)
public class SwaggerConfig implements BeanFactoryPostProcessor {
static final Logger LOG = LoggerFactory.getLogger(SwaggerConfig.class);
//扫描哪些包的controller
static final String controllerPackages = "com.*.controller";
static final boolean IS_ENABLED_SWAGGER = true;
static final String SWAGGER_AUTH_KEY = "Swagger-Auth-User";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
if ("springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander".equals(beanDefinition.getBeanClassName())) {
//去掉原来的ModelAttributeParameterExpander,会导致嵌套死循环
listableBeanFactory.removeBeanDefinition(name);
}
//为每个controller添加一个分组
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
if (annotationMetadata.hasAnnotation(Controller.class.getName()) && isFromControllerPackages(annotationMetadata.getClassName())) {
DocketFactory docketFactory = new DocketFactory(annotationMetadata.getClassName());
try {
listableBeanFactory.registerSingleton(DocketFactory.class.getName() + "." + docketFactory.groupName(), docketFactory.getObject());
} catch (Exception e) {
LOG.error("注册DocketFactory失败", e);
}
}
}
}
}
private boolean isFromControllerPackages(String className) {
for (String pk : controllerPackages.split(",")) {
if (className.startsWith(pk)) {
return true;
}
}
return false;
}
static class DocketFactory implements FactoryBean {
private String groupName;
private String controllerClassName;
public DocketFactory(String controllerClassName) {
Objects.requireNonNull(controllerClassName, "controllerClassName can not be null");
this.groupName = controllerClassName.substring(controllerClassName.lastIndexOf(".") + 1);
this.controllerClassName = controllerClassName;
}
public String groupName() {
return this.groupName;
}
@Override
public Docket getObject() throws Exception {
return new Docket(DocumentationType.SWAGGER_2)
.enable(IS_ENABLED_SWAGGER)
.groupName(groupName)
.forCodeGeneration(true).select()
.apis(controller(controllerClassName))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(setHeaderToken())
.apiInfo(apiInfo());
}
/**设置全局变量**/
private List setHeaderToken() {
ParameterBuilder tokenPar = new ParameterBuilder();
List pars = new ArrayList<>();
tokenPar.name(SWAGGER_AUTH_KEY)
.description("当前测试用户账号")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
pars.add(tokenPar.build());
return pars;
}
private Predicate controller(String controllerClassName) {
return new Predicate() {
@Override
public boolean apply(RequestHandler input) {
return input.declaringClass().getName().startsWith(controllerClassName);
}
};
}
@Override
public Class> getObjectType() {
return Docket.class;
}
@Override
public boolean isSingleton() {
return true;
}
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("API 集成测试")
.description(controllerClassName + " Swagger API Test")
.version("2.0")
.build();
return apiInfo;
}
}
}
7、由于我们删除掉原来的springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander,所以我们必须自己实现一个,否则会报错。该类都是抄过来的,只是把会导致死循环的代码去掉,有需要可以重新实现expand方法。
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Sets.newHashSet;
import static springfox.documentation.schema.Collections.collectionElementType;
import static springfox.documentation.schema.Collections.isContainerType;
import static springfox.documentation.schema.Types.typeNameFor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedField;
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.Lists;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.Maps;
import springfox.documentation.schema.Types;
import springfox.documentation.schema.property.field.FieldProvider;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.schema.AlternateTypeProvider;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeField;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander;
@Component
public class SwaggerModelAttributeParameterExpander extends ModelAttributeParameterExpander {
private static final Logger LOG = LoggerFactory.getLogger(SwaggerModelAttributeParameterExpander.class);
private final FieldProvider fieldProvider;
@Autowired
public SwaggerModelAttributeParameterExpander(FieldProvider fields) {
super(fields);
this.fieldProvider = fields;
}
@Override
public List expand(final String parentName, final ResolvedType paramType, DocumentationContext documentationContext) {
List parameters = Lists.newArrayList();
Set beanPropNames = getBeanPropertyNames(paramType.getErasedType());
Iterable fields = FluentIterable.from(fieldProvider.in(paramType))
.filter(onlyBeanProperties(beanPropNames));
LOG.debug("Expanding parameter type: {}", paramType);
AlternateTypeProvider alternateTypeProvider = documentationContext.getAlternateTypeProvider();
FluentIterable modelAttributes = FluentIterable.from(fields)
.transform(toModelAttributeField(alternateTypeProvider));
FluentIterable expendables = modelAttributes.filter(not(simpleType()))
.filter(not(recursiveType(paramType)));
for (ModelAttributeField each : expendables) {
LOG.debug("Attempting to expand expandable field: {}", each.getField());
//parameters.addAll(
// expand(nestedParentName(parentName, each.getField()), each.getFieldType(), documentationContext));
}
FluentIterable collectionTypes = modelAttributes
.filter(and(isCollection(), not(recursiveCollectionItemType(paramType))));
for (ModelAttributeField each : collectionTypes) {
LOG.debug("Attempting to expand collection/array field: {}", each.getField());
ResolvedType itemType = collectionElementType(each.getFieldType());
if (Types.isBaseType(itemType)) {
parameters.add(simpleFields(parentName, documentationContext, each));
} else {
//parameters
// .addAll(expand(nestedParentName(parentName, each.getField()), itemType, documentationContext));
}
}
FluentIterable simpleFields = modelAttributes.filter(simpleType());
for (ModelAttributeField each : simpleFields) {
parameters.add(simpleFields(parentName, documentationContext, each));
}
return FluentIterable.from(parameters).filter(not(hiddenParameters())).toList();
}
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, DocumentationContext documentationContext, 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,
each.getField(), documentationContext.getDocumentationType(), new ParameterBuilder());
return pluginsManager.expandParameter(parameterExpansionContext);
}
private Predicate recursiveType(final ResolvedType paramType) {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return equal(input.getFieldType(), paramType);
}
};
}
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 input.getFieldType().getErasedType().isEnum();
}
};
}
private Predicate belongsToJavaPackage() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return packageName(input.getFieldType().getErasedType()).startsWith("java.lang");
}
};
}
private Predicate isBaseType() {
return new Predicate() {
@Override
public boolean apply(ModelAttributeField input) {
return Types.isBaseType(input.getFieldType()) || input.getField().getType().isPrimitive();
}
};
}
private Function toModelAttributeField(
final AlternateTypeProvider alternateTypeProvider) {
return new Function() {
@Override
public ModelAttributeField apply(ResolvedField input) {
return new ModelAttributeField(fieldType(alternateTypeProvider, input), input);
}
};
}
private Predicate onlyBeanProperties(final Set beanPropNames) {
return new Predicate() {
@Override
public boolean apply(ResolvedField input) {
return beanPropNames.contains(input.getName());
}
};
}
private String nestedParentName(String parentName, ResolvedField field) {
String name = field.getName();
ResolvedType fieldType = field.getType();
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, ResolvedField field) {
return alternateTypeProvider.alternateFor(field.getType());
}
private String packageName(Class> type) {
return Optional.fromNullable(type.getPackage()).transform(toPackageName()).or("java");
}
private Function toPackageName() {
return new Function() {
@Override
public String apply(Package input) {
return input.getName();
}
};
}
private Set getBeanPropertyNames(final Class> clazz) {
try {
Set beanProps = new HashSet();
PropertyDescriptor[] propDescriptors = getBeanInfo(clazz).getPropertyDescriptors();
for (PropertyDescriptor propDescriptor : propDescriptors) {
if (propDescriptor.getReadMethod() != null && propDescriptor.getWriteMethod() != null) {
beanProps.add(propDescriptor.getName());
}
}
return beanProps;
} catch (IntrospectionException e) {
LOG.warn(String.format("Failed to get bean properties on (%s)", clazz), e);
}
return newHashSet();
}
@VisibleForTesting
BeanInfo getBeanInfo(Class> clazz) throws IntrospectionException {
return Introspector.getBeanInfo(clazz);
}
}
8、总结,这个东西折腾了很久才弄出来,特别是ModelAttributeParameterExpander会死循环还不能拓展,里面的实现方法基本都是private,所以只能抄一份出来改,而且还没办法覆盖springfox自定义的,只能暴力删除。springfox2.9.2是有解决死循环的问题的,如果大家的spring版本在4.0以上,直接用springfox2.9.2,那么多坑要填。还有就是controller比较多的时候要跟我一样分组,或者自定义分组,不然全部加载出来页面就崩了,加载时间也会很感人。