您的当前位置:首页正文

SpringBoot整合Swagger2,代码文档一手抓

2024-11-23 来源:个人技术集锦

引言

什么是swagger

Swagger 是一个规范且完整的,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档。
Swagger 的目标是对 REST [API] 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。

Swagger 的优势

支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。
前后端集成联调,前端人员和后端人员无法做到 “及时协商,尽早解决”,通过swagger可以直观展现实时调整,减少沟通成本。

SpringBoot整合Swagger 2

添加Swagger依赖

        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.7</version>
        </dependency>

        <!--测试组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--添加Spring swagger 依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

application.yml配置

server:
  port: 17006

配置类SwaggerConfig

/**
 * Swagger配置类
 * @author 共饮一杯无
 */
//指定为配置类
@Configuration
//开启Swagger2
@EnableSwagger2
public class SwaggerConfig {
    /**
     * 添加摘要信息(Docket)
     */
    @Bean
    public Docket controllerApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title("API文档标题")
                        .description("文档描述:用于实时查看、测试API")
                        .contact(new Contact("共饮一杯无", "https://zhanjq./", ""))
                        .version("版本号:1.0")
                        .build())
                .select()
                //API基础扫描路径
                .apis(RequestHandlerSelectors.basePackage("com.zjq.swagger"))
                .paths(PathSelectors.any())
                .build();
    }
}

启动类配置

/**
 * SpringSwagger案例
 * @author zjq
 */
@SpringBootApplication
//启用swagger
@EnableSwagger2
public class SpringSwaggerApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringSwaggerApplication.class, args);
	}

}

RESTful实战案例参考

带swagger注解的Controller类:
带swagger注解的实体类:

常见swagger注解说明

@Api(value = "用户管理", description = "系统中的用户操作")
  1. @ApiOperation: 这个注解用于方法级别,描述一个特定的API操作。例如:
@ApiOperation(value = "获取所有用户", notes = "返回所有用户列表")
  1. @ApiParam: 这个注解用于描述方法参数的细节,如用途、是否必需、默认值等。例如:
@ApiParam(value = "用户ID", required = true)
  1. @ApiResponse: 描述API的响应状态码及可能的响应体。例如:
@ApiResponse(code = 200, message = "成功获取用户", response = User.class)
  1. @ApiResponses: 包含多个@ApiResponse,可以描述多个可能的响应情况。例如:
@ApiResponses(value = {
    @ApiResponse(code = 200, message = "成功"),
    @ApiResponse(code = 404, message = "未找到资源"),
    @ApiResponse(code = 500, message = "服务器错误")
})
  1. @ApiIgnore: 如果你不希望某个方法出现在Swagger文档中,可以使用此注解。例如:
@ApiIgnore
@GetMapping("/test")
public String test() {
    return "This is a test endpoint";
}
  1. @ApiModel: 用于描述一个模型(即类)的结构。例如:
@ApiModel(value = "用户模型", description = "用户数据模型")
public class User {
}
  1. @ApiModelProperty: 描述模型属性的细节。例如:
@ApiModelProperty(value = "用户ID", example = "1")
private Integer id;
  1. @ApiImplicitParam

这个注解通常用于方法级别,而不是参数级别,它描述的是隐式参数,即那些可能不直接从方法签名中明显看出的参数。
它可以用来描述查询参数 (query parameters)、请求头 (headers) 或者其他非显式的参数类型。
当你需要描述多个这样的参数时,可以使用 @ApiImplicitParams,这是一个 @ApiImplicitParam 的数组。例如:

@GetMapping("/users")
@ApiImplicitParams({
    @ApiImplicitParam(name = "page", value = "Page number of the result set", paramType = "query", dataType = "integer"),
    @ApiImplicitParam(name = "limit", value = "Size of the page", paramType = "query", dataType = "integer")
})
public List<User> getUsers(@RequestParam Integer page, @RequestParam Integer limit) {
    // ...
}

@ApiParam 更适用于描述与方法签名直接相关的参数,而 @ApiImplicitParam 更适合描述那些通过其他方式(如请求查询字符串或头部)传递的参数。
@ApiImplicitParam 提供了一种方法来集中描述多个参数,特别是在参数数量较多的情况下,这比在每个参数上使用 @ApiParam 要更整洁。

页面访问效果

常见错误

  1. 使用 Spring Boot 2.7 及以上版本时,Swagger 2.9.2会有一些**兼容性问题,**报错如下:

Caused by: java.lang.NullPointerException: null
at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:112) ~[springfox-spi-2.9.2.jar:null]
at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:109) ~[springfox-spi-2.9.2.jar:null]

解决办法:
版本对应(Maven官方仓库:)

  • SPB(SpringBoot) 2.7 以下 + Swagger-ui 和Swagger2 2.9.2

  • SPB(SpringBoot) 2.7 往上 + springfox-boot-starter(3.0.0包括Swagger-ui 和Swagger2 3.0.0)

  • SPB(SpringBoot) 3.0往上直接改用Springdoc OpenAPI

  1. 使用springboot2.7以上版本+springfox-boot-starter3.0.0依旧报错
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.27.jar:5.3.27]
	at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_171]
	at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:937) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.27.jar:5.3.27]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.11.jar:2.7.11]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) [spring-boot-2.7.11.jar:2.7.11]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.11.jar:2.7.11]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.7.11.jar:2.7.11]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) [spring-boot-2.7.11.jar:2.7.11]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) [spring-boot-2.7.11.jar:2.7.11]
	at com.zjq.swagger.SpringSwaggerApplication.main(SpringSwaggerApplication.java:16) [classes/:na]
Caused by: java.lang.NullPointerException: null
	at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0]
	at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) ~[springfox-core-3.0.0.jar:3.0.0]
	at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) ~[springfox-spi-3.0.0.jar:3.0.0]
	at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469) ~[na:1.8.0_171]
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:1.8.0_171]
	at java.util.TimSort.sort(TimSort.java:220) ~[na:1.8.0_171]
	at java.util.Arrays.sort(Arrays.java:1512) ~[na:1.8.0_171]
	at java.util.ArrayList.sort(ArrayList.java:1462) ~[na:1.8.0_171]
	at java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:387) ~[na:1.8.0_171]
	at java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:1.8.0_171]
	at java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:1.8.0_171]
	at java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:1.8.0_171]
	at java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:1.8.0_171]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) ~[na:1.8.0_171]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_171]
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_171]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_171]
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_171]

解决办法:
在配置类注入如下bean:

    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
                List<T> copy = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }

            @SuppressWarnings("unchecked")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }

参考:
官方网站:
官方文档:

本文内容到此结束了,
如有收获欢迎点赞?收藏?关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问?欢迎各位指出。
主页

保持热爱,奔赴下一场山海。???

显示全文