springfox-swagger2 2.6.1 整合springmvc 3.2

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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比较多的时候要跟我一样分组,或者自定义分组,不然全部加载出来页面就崩了,加载时间也会很感人。

转载于:https://my.oschina.net/linchuhao23/blog/2396125

你可能感兴趣的:(springfox-swagger2 2.6.1 整合springmvc 3.2)