Spring Boot项目中常使用springfox-swagger来生成REST API文档,使用springfox-swagger-ui进行API测试。
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
REST API方法的参数含有org.springframework.data.domain.Pageable时,如未进行其它配置,Swagger根据接口Pageable的get/is方法生成了pageNumber、pageSize、offset、paged、unpaged、sort.sorted、sort.unsorted等参数,但实际上这些参数是无效的。
@ApiOperation(value = "Find airlines")
@GetMapping(value = "/airlines")
public Page<Airline> searchAirlines(Airline airline, Pageable pageable) {
return repository.findAll(org.springframework.data.domain.Example.of(airline), pageable);
}
Spring Boot解析Pageable参数的过程请查看org.springframework.data.web.PageableHandlerMethodArgumentResolver的resolveArgument()方法:
@Override
public Pageable resolveArgument(MethodParameter methodParameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
assertPageableUniqueness(methodParameter);
Optional<Pageable> defaultOrFallback = getDefaultFromAnnotationOrFallback(methodParameter).toOptional();
String pageString = webRequest.getParameter(getParameterNameToUse(pageParameterName, methodParameter));
String pageSizeString = webRequest.getParameter(getParameterNameToUse(sizeParameterName, methodParameter));
Optional<Integer> page = parseAndApplyBoundaries(pageString, Integer.MAX_VALUE, true);
Optional<Integer> pageSize = parseAndApplyBoundaries(pageSizeString, maxPageSize, false);
if (!(page.isPresent() && pageSize.isPresent()) && !defaultOrFallback.isPresent()) {
return Pageable.unpaged();
}
int p = page.orElseGet(() -> defaultOrFallback.map(Pageable::getPageNumber).orElseThrow(IllegalStateException::new));
int ps = pageSize.orElseGet(() -> defaultOrFallback.map(Pageable::getPageSize).orElseThrow(IllegalStateException::new));
// Limit lower bound
ps = ps < 1 ? defaultOrFallback.map(Pageable::getPageSize).orElseThrow(IllegalStateException::new) : ps;
// Limit upper bound
ps = ps > maxPageSize ? maxPageSize : ps;
Sort sort = sortResolver.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
return PageRequest.of(p, ps,
sort.isSorted() ? sort : defaultOrFallback.map(Pageable::getSort).orElseGet(Sort::unsorted));
}
其中使用的参数名称为page、size、sort,因此通过Swagger传入的参数是无效的。解决方法有以下几种:
使用@ApiImplicitParams隐式添加page、size、sort参数,使用@ApiIgnore忽略Pageable参数。缺点,每个方法都要添加,比较繁琐。
@ApiOperation(value = "Find airlines")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", dataType = "integer", paramType = "query", value = "Results page you want to retrieve (0..N)"),
@ApiImplicitParam(name = "size", dataType = "integer", paramType = "query", value = "Number of records per page."),
@ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query",
value = "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
})
@GetMapping(value = "/airlines")
public Page<Airline> searchAirlines(Airline airline, @ApiIgnore Pageable pageable) {
return repository.findAll(org.springframework.data.domain.Example.of(airline), pageable);
}
添加OperationBuilderPlugin组件,遇到Pageable时自动添加page、size、sort参数,同时也需使用@ApiIgnore忽略Pageable参数。
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.ResolvedTypes;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.spi.schema.contexts.ModelContext.inputParam;
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class PageableParameterReader implements OperationBuilderPlugin {
private final TypeNameExtractor nameExtractor;
private final TypeResolver resolver;
private final ResolvedType pageableType;
@Autowired
public PageableParameterReader(TypeNameExtractor nameExtractor, TypeResolver resolver) {
this.nameExtractor = nameExtractor;
this.resolver = resolver;
this.pageableType = resolver.resolve(Pageable.class);
}
@Override
public void apply(OperationContext context) {
List<ResolvedMethodParameter> methodParameters = context.getParameters();
List<Parameter> parameters = newArrayList();
for (ResolvedMethodParameter methodParameter : methodParameters) {
ResolvedType resolvedType = methodParameter.getParameterType();
if (pageableType.equals(resolvedType)) {
ParameterContext parameterContext = new ParameterContext(methodParameter,
new ParameterBuilder(),
context.getDocumentationContext(),
context.getGenericsNamingStrategy(),
context);
Function<ResolvedType, ? extends ModelReference> factory = createModelRefFactory(parameterContext);
ModelReference intModel = factory.apply(resolver.resolve(Integer.TYPE));
ModelReference stringModel = factory.apply(resolver.resolve(List.class, String.class));
parameters.add(new ParameterBuilder()
.parameterType("query")
.name("page")
.modelRef(intModel)
.description("Results page you want to retrieve (0..N)").build());
parameters.add(new ParameterBuilder()
.parameterType("query")
.name("size")
.modelRef(intModel)
.description("Number of records per page").build());
parameters.add(new ParameterBuilder()
.parameterType("query")
.name("sort")
.modelRef(stringModel)
.allowMultiple(true)
.description("Sorting criteria in the format: property(,asc|desc). "
+ "Default sort order is ascending. "
+ "Multiple sort criteria are supported.")
.build());
context.operationBuilder().parameters(parameters);
}
}
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
private Function<ResolvedType, ? extends ModelReference> createModelRefFactory(ParameterContext context) {
ModelContext modelContext = inputParam(
context.getGroupName(),
context.resolvedMethodParameter().getParameterType(),
context.getDocumentationType(),
context.getAlternateTypeProvider(),
context.getGenericNamingStrategy(),
context.getIgnorableParameterTypes());
return ResolvedTypes.modelRefFactory(modelContext, nameExtractor);
}
}
import com.fasterxml.classmate.TypeResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import springfox.documentation.builders.AlternateTypeBuilder;
import springfox.documentation.builders.AlternateTypePropertyBuilder;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import java.lang.reflect.Type;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
@Configuration
@SuppressWarnings("SpringJavaAutowiringInspection")
public class SpringDataConfiguration {
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin())));
}
};
}
private Type pageableMixin() {
return new AlternateTypeBuilder()
.fullyQualifiedClassName(String.format("%s.generated.%s", Pageable.class.getPackage().getName(), Pageable.class.getSimpleName()))
.withProperties(newArrayList(property(Integer.class, "page"), property(Integer.class, "size"), property(String.class, "sort")))
.build();
}
private AlternateTypePropertyBuilder property(Class<?> type, String name) {
return new AlternateTypePropertyBuilder()
.withName(name)
.withType(type)
.withCanRead(true)
.withCanWrite(true);
}
}
这是springfox文档中介绍的方法,这种方法创建了一个in-memory type,但我未找到添加说明的方法。可以自定义一个Page class:
import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
@Configuration
@SuppressWarnings("SpringJavaAutowiringInspection")
public class SpringDataConfiguration {
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
}
};
}
@ApiModel
static class Page {
@ApiModelProperty("Results page you want to retrieve (0..N)")
private Integer page;
@ApiModelProperty("Number of records per page")
private Integer size;
@ApiModelProperty("Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
private List<String> sort;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public List<String> getSort() {
return sort;
}
public void setSort(List<String> sort) {
this.sort = sort;
}
}
}
Swagger IO
Swagger Github
Tools and Integrations
SpringFox
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。