千家信息网

Spring Boot集成SpringFox Swagger的Pageable参数问题

发表于:2025-01-23 作者:千家信息网编辑
千家信息网最后更新 2025年01月23日,Spring Boot项目中常使用springfox-swagger来生成REST API文档,使用springfox-swagger-ui进行API测试。 io.springfox sp
千家信息网最后更新 2025年01月23日Spring Boot集成SpringFox Swagger的Pageable参数问题

Spring Boot项目中常使用springfox-swagger来生成REST API文档,使用springfox-swagger-ui进行API测试。

    io.springfox    springfox-swagger2    2.8.0    io.springfox    springfox-swagger-ui    2.8.0

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 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()方法:

@Overridepublic Pageable resolveArgument(MethodParameter methodParameter, @Nullable ModelAndViewContainer mavContainer,        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {    assertPageableUniqueness(methodParameter);    Optional defaultOrFallback = getDefaultFromAnnotationOrFallback(methodParameter).toOptional();    String pageString = webRequest.getParameter(getParameterNameToUse(pageParameterName, methodParameter));    String pageSizeString = webRequest.getParameter(getParameterNameToUse(sizeParameterName, methodParameter));    Optional page = parseAndApplyBoundaries(pageString, Integer.MAX_VALUE, true);    Optional 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

使用@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 searchAirlines(Airline airline, @ApiIgnore Pageable pageable) {    return repository.findAll(org.springframework.data.domain.Example.of(airline), pageable);}

OperationBuilderPlugin

添加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 methodParameters = context.getParameters();        List 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 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 createModelRefFactory(ParameterContext context) {        ModelContext modelContext = inputParam(                context.getGroupName(),                context.resolvedMethodParameter().getParameterType(),                context.getDocumentationType(),                context.getAlternateTypeProvider(),                context.getGenericNamingStrategy(),                context.getIgnorableParameterTypes());        return ResolvedTypes.modelRefFactory(modelContext, nameExtractor);    }}

AlternateTypeRuleConvention

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 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 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 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 getSort() {            return sort;        }        public void setSort(List sort) {            this.sort = sort;        }    }}

参考文档

Swagger IO
Swagger Github
Tools and Integrations
SpringFox

0