Swagger 整合 springboot 2.6.8 + swagger3 springboot 2.x + swagger2

拓展阅读

Devops-01-devops 是什么?

Devops-02-Jpom 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件

代码质量管理 SonarQube-01-入门介绍

项目管理平台-01-jira 入门介绍 缺陷跟踪管理系统,为针对缺陷管理、任务追踪和项目管理的商业性应用软件

项目管理平台-01-Phabricator 入门介绍 一套集成的强大工具,帮助公司构建更高质量的软件

持续集成平台 01 jenkins 入门介绍

Swagger 整合 springmvc

Swagger 整合 springboot 2.6.8 + swagger3 springboot 2.x + swagger2

Swagger 文档工具 设计、构建、文档化和使用您的 RESTful API

springboot 2.x + swagger2

maven

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

编写配置类

@Configuration
@EnableSwagger2 // 开启Swagger2自动配置
public class Swagger2Config {
    
    @Bean
    public Docket UserApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("UserApi") // 分组
                .apiInfo(UserApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.ken.login.controller"))
                .paths(PathSelectors.ant("/user/**"))
                .build();
    }

    @Bean
    public Docket BlogApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("BlogApi") // 分组
                .apiInfo(BlogApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.ken.login.controller"))
                .paths(PathSelectors.ant("/blog/**"))
                .build();
    }

    // 配置用户文档信息
    private ApiInfo UserApiInfo(){
        return new ApiInfoBuilder()
                .title("我的API文档") // 标题
                .description("本文档描述了用户相关的接口定义") // 描述
                .version("1.0") // 版本
                .contact(new Contact("联系人名字", "联系人访问链接", "联系人邮箱")) // 联系人信息
                .build();
    }
    
    // 配置博客文档信息
    private ApiInfo BlogApiInfo(){
        return new ApiInfoBuilder()
                .title("我的API文档") // 标题
                .description("本文档描述了博客相关的接口定义") // 描述
                .version("1.0") // 版本
                .contact(new Contact("联系人名字", "联系人访问链接", "联系人邮箱")) // 联系人信息
                .build();
    }

}

访问

使用注解对接口进行描述(也可以省略)

访问 http://localhost:8080/swagger-ui.html

启动报错

1. NPE

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]
    at com.google.common.collect.ComparatorOrdering.compare(ComparatorOrdering.java:37) ~[guava-20.0.jar:na]
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351) ~[na:1.8.0_05]
    at java.util.TimSort.sort(TimSort.java:216) ~[na:1.8.0_05]
    at java.util.Arrays.sort(Arrays.java:1435) ~[na:1.8.0_05]
    at com.google.common.collect.Ordering.sortedCopy(Ordering.java:855) ~[guava-20.0.jar:na]
    at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:57) ~[springfox-spring-web-2.9.2.jar:null]
    at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:138) ~[springfox-spring-web-2.9.2.jar:null]
    at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:135) ~[springfox-spring-web-2.9.2.jar:null]
    at com.google.common.collect.Iterators$7.transform(Iterators.java:750) ~[guava-20.0.jar:na]
    at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
    at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
    at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:52) ~[guava-20.0.jar:na]
    at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:50) ~[guava-20.0.jar:na]
    at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:249) ~[guava-20.0.jar:na]
    at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:209) ~[guava-20.0.jar:na]
    at com.google.common.collect.FluentIterable.toList(FluentIterable.java:614) ~[guava-20.0.jar:na]
    at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.defaultContextBuilder(DocumentationPluginsBootstrapper.java:111) ~[springfox-spring-web-2.9.2.jar:null]
    at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.buildContext(DocumentationPluginsBootstrapper.java:96) ~[springfox-spring-web-2.9.2.jar:null]
    at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167) ~[springfox-spring-web-2.9.2.jar:null]
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.16.jar:5.3.16]
    ... 15 common frames omitted

原因:

Spring Boot 2.6及 更高版本使用的是PathPatternMatcher,而Springfox使用的路径匹配是基于AntPathMatcher的,所以更改配置如下:

spring:
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER

2. 访问页面 404

@EnableWebMvc 注解的问题,加了这个注解以后会导致静态资源路径无法访问。

继承了WebMvcConfigurationSupport,配置文件在中配置的相关内容会失效,需要重新指定静态资源

@EnableWebMvc注解(相当于继承WebMvcConfigurationSupport)和extends WebMvcConfigurationSupport导致404的原因都是因为同时有多个配置类实现了WebMvcConfigurer或继承了WebMvcConfigurationSupport的话,只会有一个生效,即以上两种情况导致了默认的WebMvcAutoConfiguration自动配置失效,故找不到静态资源。

解决办法是,保持一个配置类,将配置都在一个类中设置。

解决方法:

在一个统一的WebConfig中实现WebMvcConfigurer,并重写其中的public void addResourceHandlers(ResourceHandlerRegistry registry)方法,重新指定swagger静态资源,如下:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

3、页面被拦截器拦截

放行该页面,如下:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .excludePathPatterns("user/login")
                .excludePathPatterns("user/register")
                .excludePathPatterns("/swagger-resources/**")
                .excludePathPatterns("/swagger-ui.html/**")
                .excludePathPatterns("/webjars/**");
    }
}

快速开始

maven

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>

配置类

@Configuration  //注解为配置类,向spring容器注入bean对象
@EnableOpenApi
public class SwaggerConfig {
    @Bean  //把方法的返回值对象,注入容器
    public Docket docket(){
        return new Docket(DocumentationType.OAS_30)//选择swagger3.0版本号
                .apiInfo(apiInfo()).enable(true) //获取版权声明
                .select()
                //确定swagger能够访问到的请求接口范围,指定控制器包路径
                .apis(RequestHandlerSelectors.basePackage("org.example.controller"))
                .paths(PathSelectors.any())
                .build();  //构建对象
    }
    //撰写项目的版本声明
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("医院预约挂号系统") //项目的名称
                .description("这是一个预约系统") //项目功能说明
                .contact(new Contact("自己的名字","自己的博客网址","12141579@qq.com"))
                .version("1.0")
                .build();
    }
}

访问路径

运行成功后的访问路径:

http://localhost:8080/swagger-ui/index.html

踩坑版本

maven

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

配置类

package com.posinda.system.infrastructure.swagger;
 
 
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
 
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
 
/**
 * @ClassName : Swagger3Config
 * @Description :
 * @Author : felix
 * @Date: 2022-06-07 13:57
 */
@EnableOpenApi
@Configuration
public class Swagger3Config {
 
   /**
    * 创建API
    */
   @Bean
   public Docket createRestApi()
   {
      return new Docket(DocumentationType.OAS_30)
            // 是否启用Swagger
            .enable(true)
            // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
            .apiInfo(apiInfo())
            // 设置哪些接口暴露给Swagger展示
            .select()
            // 扫描所有有注解的api,用这种方式更灵活
            .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
            // 扫描指定包中的swagger注解
//           .apis(RequestHandlerSelectors.basePackage("com.posinda.admin.peopleService.controller"))
            // 扫描所有
//           .apis(RequestHandlerSelectors.any())
//          .paths(PathSelectors.any())
            .build()
            /* 设置安全模式,swagger可以设置访问token */
//          .securitySchemes(securitySchemes())
//          .securityContexts(securityContexts())
//          .pathMapping(pathMapping)
            ;
   }
 
   /**
    * 添加摘要信息
    */
   private ApiInfo apiInfo()
   {
      // 用ApiInfoBuilder进行定制
      return new ApiInfoBuilder()
            // 设置标题
            .title("标题:若依管理系统_接口文档")
            // 描述
            .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
            // 作者信息
            .contact(new Contact("felix", null, null))
            // 版本
            .version("版本号:" + "1.0")
            .build();
   }
 
 
   /**
    * 安全模式,这里指定token通过Authorization头请求头传递
    */
   private List<SecurityScheme> securitySchemes()
   {
      List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
      apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
      return apiKeyList;
   }
 
   /**
    * 安全上下文
    */
   private List<SecurityContext> securityContexts()
   {
      List<SecurityContext> securityContexts = new ArrayList<>();
      securityContexts.add(
            SecurityContext.builder()
                  .securityReferences(defaultAuth())
                  .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                  .build());
      return securityContexts;
   }
 
   /**
    * 默认的安全上引用
    */
   private List<SecurityReference> defaultAuth()
   {
      AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
      AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
      authorizationScopes[0] = authorizationScope;
      List<SecurityReference> securityReferences = new ArrayList<>();
      securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
      return securityReferences;
   }
 
   /**
    * 解决springboot2.6 和springfox不兼容问题  Failed to start bean ‘ documentationPluginsBootstrapper ‘ ; nested exception…
    * @return
    */
   @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);
            }
         }
      };
   }
 
 
}

一些问题

Failed to start bean ‘ documentationPluginsBootstrapper ‘ ; nested exception…

  1. 不兼容解决办法
/**
    * 解决springboot2.6 和springfox不兼容问题  Failed to start bean ‘ documentationPluginsBootstrapper ‘ ; nested exception…
    * @return
    */
   @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);
            }
         }
      };
   }
  1. yml 配置文件
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher #swagger3 需配置,不然展示不了列表

或者指定下面的注解:

@EnableMvc

参考资料

Springboot集成Swagger

https://blog.csdn.net/yao22yao/article/details/125207679

https://blog.csdn.net/qq_25046827/article/details/124086625

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容