springfox-swagger2 2.6.1 整合springmvc 3.2

一、加入springfox依賴html

<dependency>
		    <groupId>io.springfox</groupId>
		    <artifactId>springfox-swagger2</artifactId>
		    <version>2.6.1</version>
		</dependency>
		<dependency>
		    <groupId>io.springfox</groupId>
		    <artifactId>springfox-swagger-ui</artifactId>
		    <version>2.6.1</version>
		</dependency>

二、配置org.springframework.web.accept.ContentNegotiationManagerFactoryBean,不然會報錯。確保applicationContext.xml 中 xsi:schemaLocation 的 http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 爲3.2以上版本java

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
    
    <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
	    <property name="favorPathExtension" value="false" />
	    <property name="favorParameter" value="true" />
	    <property name="parameterName" value="mediaType" />
	    <property name="ignoreAcceptHeader" value="true"/>
	    <property name="useJaf" value="false"/>
	    <property name="defaultContentType" value="application/json" />
	    <property name="mediaTypes">
	        <map>
	            <entry key="json" value="application/json" />
	            <entry key="xml" value="application/xml" />
	       </map>
	    </property>
	</bean>

三、配置DispatcherServlet,DispatcherServlet的攔截鏈接必須爲 / ,或者沒法訪問springfox-swagger的資源web

<servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

	<servlet-mapping>
		<servlet-name>DispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

四、在applicationContext.xml中配置 <mvc:default-servlet-handler/>,或者沒法訪問項目的靜態資源spring

<mvc:default-servlet-handler/>

五、在 applicationContext.xml 配置訪問springfox-swagger-ui 的靜態資源json

<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
    <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />

六、編寫swagger配置類,確保<context:component-scan/> 掃描獲得api

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<Docket> {

		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<Parameter> setHeaderToken() {
	        ParameterBuilder tokenPar = new ParameterBuilder();
	        List<Parameter> 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<RequestHandler> controller(String controllerClassName) {
			return new Predicate<RequestHandler>() {
				@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;
		}

	}
	
}

七、因爲咱們刪除掉原來的springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander,因此咱們必須本身實現一個,不然會報錯。該類都是抄過來的,只是把會致使死循環的代碼去掉,有須要能夠從新實現expand方法。spring-mvc

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<Parameter> expand(final String parentName, final ResolvedType paramType, DocumentationContext documentationContext) {
		List<Parameter> parameters = Lists.newArrayList();
		Set<String> beanPropNames = getBeanPropertyNames(paramType.getErasedType());
		Iterable<ResolvedField> fields = FluentIterable.from(fieldProvider.in(paramType))
				.filter(onlyBeanProperties(beanPropNames));
		LOG.debug("Expanding parameter type: {}", paramType);
		AlternateTypeProvider alternateTypeProvider = documentationContext.getAlternateTypeProvider();

		FluentIterable<ModelAttributeField> modelAttributes = FluentIterable.from(fields)
				.transform(toModelAttributeField(alternateTypeProvider));

		FluentIterable<ModelAttributeField> 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<ModelAttributeField> 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<ModelAttributeField> simpleFields = modelAttributes.filter(simpleType());
		for (ModelAttributeField each : simpleFields) {
			parameters.add(simpleFields(parentName, documentationContext, each));
		}
		return FluentIterable.from(parameters).filter(not(hiddenParameters())).toList();
	}

	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, 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<ModelAttributeField> recursiveType(final ResolvedType paramType) {
		return new Predicate<ModelAttributeField>() {
			@Override
			public boolean apply(ModelAttributeField input) {
				return equal(input.getFieldType(), paramType);
			}
		};
	}

	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 input.getFieldType().getErasedType().isEnum();
			}
		};
	}

	private Predicate<ModelAttributeField> belongsToJavaPackage() {
		return new Predicate<ModelAttributeField>() {
			@Override
			public boolean apply(ModelAttributeField input) {
				return packageName(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.getField().getType().isPrimitive();
			}
		};
	}

	private Function<ResolvedField, ModelAttributeField> toModelAttributeField(
			final AlternateTypeProvider alternateTypeProvider) {
		return new Function<ResolvedField, ModelAttributeField>() {
			@Override
			public ModelAttributeField apply(ResolvedField input) {
				return new ModelAttributeField(fieldType(alternateTypeProvider, input), input);
			}
		};
	}

	private Predicate<ResolvedField> onlyBeanProperties(final Set<String> beanPropNames) {
		return new Predicate<ResolvedField>() {
			@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<Package, String> toPackageName() {
		return new Function<Package, String>() {
			@Override
			public String apply(Package input) {
				return input.getName();
			}
		};
	}

	private Set<String> getBeanPropertyNames(final Class<?> clazz) {

		try {
			Set<String> beanProps = new HashSet<String>();
			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);
	}

}

八、總結,這個東西折騰了好久才弄出來,特別是ModelAttributeParameterExpander會死循環還不能拓展,裏面的實現方法基本都是private,因此只能抄一份出來改,並且還沒辦法覆蓋springfox自定義的,只能暴力刪除。springfox2.9.2是有解決死循環的問題的,若是你們的spring版本在4.0以上,直接用springfox2.9.2,那麼多坑要填。還有就是controller比較多的時候要跟我同樣分組,或者自定義分組,否則所有加載出來頁面就崩了,加載時間也會很感人。mvc

相關文章
相關標籤/搜索