目录
一、依赖
二、集成Swagger Java Config
三、配置完毕
四、解决方案
彩蛋
想尝鲜,坑也多,一起入个坑~
一、依赖
SpringBoot版本:2.7.14
Swagger版本:3.0.0
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.3</version>
</dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version>
</dependency>
二、集成Swagger Java Config
@Value("${server.port:8080}")
private String port;@Value("${server.servlet.context-path:}")
private String rootPath;@Bean
Docket docket(SwaggerProperties properties) {Docket docket = new Docket(DocumentationType.OAS_30).apiInfo(apiInfo(properties)).groupName(properties.getGroupName()).select().apis(scanBasePackages(properties.getBasePackage())).paths(PathSelectors.any()).build().globalRequestParameters(globalRequestParameters(properties)).globalResponses(HttpMethod.POST, responses()).globalResponses(HttpMethod.GET, responses()).pathMapping("/");log.info("Swagger3 successfully started: http://{}:{}{}/doc.html", IPUtils.getLocalIP(), port, rootPath);return docket;
}@Bean
public ModelPropertyBuilderPlugin modelPropertyBuilderPlugin() {return new DictPropertyPlugin();
}/*** 构建响应状态码*/
private List<Response> responses() {List<Response> responses = new LinkedList<>();responses.add(new ResponseBuilder().code("S").description("响应成功").build());responses.add(new ResponseBuilder().code("E").description("非'S'即为响应失败").build());return responses;
}private ApiInfo apiInfo(SwaggerProperties properties) {return new ApiInfoBuilder().title(properties.getTitle()).description(properties.getDescription()).license(properties.getLicense()).licenseUrl(properties.getLicenseUrl()).termsOfServiceUrl(properties.getTermsOfServiceUrl()).contact(new Contact(properties.getContact().getName(), properties.getContact().getUrl(), properties.getContact().getEmail())).version(properties.getVersion()).build();
}/*** 自定义请求参数** @return - list*/
private List<RequestParameter> globalRequestParameters(SwaggerProperties properties) {List<RequestParameter> params = new ArrayList<>();properties.getParams().forEach(e -> {RequestParameter parameter = new RequestParameterBuilder().name(e.getName()).description(e.getDesc()).required(e.isRequired()).in(e.getParamType()).hidden(e.isHidden()).build();params.add(parameter);});return params;
}/*** 多包扫描支持,扫描的包生成{@linkplain Predicate < RequestHandler >}** @param basePackages - 扫描的包*/
private Predicate<RequestHandler> scanBasePackages(final String... basePackages) {if (basePackages == null || basePackages.length == 0) {throw new IllegalArgumentException("basePackages不能为空");}Predicate<RequestHandler> predicate = null;for (int i = basePackages.length - 1; i >= 0; i--) {String strBasePackage = basePackages[i];if (StrUtil.isNotBlank(strBasePackage)) {Predicate<RequestHandler> tempPredicate = RequestHandlerSelectors.basePackage(strBasePackage);predicate = predicate == null ? tempPredicate : predicate.or(tempPredicate);}}if (predicate == null) {throw new IllegalArgumentException("basePackage配置不正确");}return predicate;
}
/*** swagger3 自定义展示枚举类型信息*/
public class DictPropertyPlugin implements ModelPropertyBuilderPlugin {private final Logger log = LoggerFactory.getLogger(getClass());@Overridepublic void apply(ModelPropertyContext ctx) {Optional<BeanPropertyDefinition> opt = ctx.getBeanPropertyDefinition();opt.ifPresent(bean -> {Class<?> cls = bean.getRawPrimaryType();if (IDict.class.isAssignableFrom(cls) && Enum.class.isAssignableFrom(cls)) {if (cls.getEnumConstants() == null) {return;}try {Field f = PropertySpecificationBuilder.class.getDeclaredField("description");f.setAccessible(true);String prefix = cls.getSimpleName() + "(" + f.get(ctx.getSpecificationBuilder()) + ")【";StringJoiner join = new StringJoiner(",", prefix, "】");for (IDict<?, ?> d : (IDict<?, ?>[]) cls.getEnumConstants()) {join.add(d.getDesc() + "[" + d.getCode() + "]-" + ((Enum<?>) d).name());}ctx.getSpecificationBuilder().description(join.toString());} catch (Exception e) {log.error("字典值处理失败:{}", cls.getName(), e);}}});}@Overridepublic boolean supports(DocumentationType type) {return DocumentationType.OAS_30.equals(type);}
}
properties
package com.muchenx.common.swagger.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import springfox.documentation.service.ParameterType;import java.util.ArrayList;
import java.util.List;@ConfigurationProperties(prefix = "swagger")
public class SwaggerProperties {/*** swagger会解析的包路径**/private String basePackage = "com.muchenx";/*** 分组名*/private String groupName = "default";/*** 标题**/private String title = "MuchenX";/*** 描述**/private String description = "MuchenX Cloud Project supports by Spring Cloud Alibaba";/*** 版本**/private String version = "v1.0";/*** 许可证**/private String license = "";/*** 许可证URL**/private String licenseUrl = "";/*** 服务条款URL**/private String termsOfServiceUrl = "";/*** host信息**/private String host = "";/*** 联系人信息*/private Contact contact = new Contact();/*** 自定义参数*/private List<Param> params = new ArrayList<>();public String getGroupName() {return groupName;}public void setGroupName(String groupName) {this.groupName = groupName;}public String getBasePackage() {return basePackage;}public void setBasePackage(String basePackage) {this.basePackage = basePackage;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getLicense() {return license;}public void setLicense(String license) {this.license = license;}public String getLicenseUrl() {return licenseUrl;}public void setLicenseUrl(String licenseUrl) {this.licenseUrl = licenseUrl;}public String getTermsOfServiceUrl() {return termsOfServiceUrl;}public void setTermsOfServiceUrl(String termsOfServiceUrl) {this.termsOfServiceUrl = termsOfServiceUrl;}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public Contact getContact() {return contact;}public void setContact(Contact contact) {this.contact = contact;}public List<Param> getParams() {return params;}public void setParams(List<Param> params) {this.params = params;}public static class Contact {/*** 联系人**/private String name = "Ian Geng";/*** 联系人url**/private String url = "www.muchenx.com";/*** 联系人email**/private String email = "gzhygz@gmail.com";public String getName() {return name;}public void setName(String name) {this.name = name;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}}public static class Param {// 请求参数名private String name;// 请求参数描述private String desc;/*** 请求参数类型:QUERY("query"),HEADER("header"),PATH("path"),* COOKIE("cookie"),FORM("form"),FORMDATA("formData"),BODY("body");*/private ParameterType paramType = ParameterType.HEADER;// 请求参数默认值private String defaultValue = "";// 是否必填private boolean required = false;// 是否隐藏private boolean hidden = false;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public ParameterType getParamType() {return paramType;}public void setParamType(ParameterType paramType) {this.paramType = paramType;}public String getDefaultValue() {return defaultValue;}public void setDefaultValue(String defaultValue) {this.defaultValue = defaultValue;}public boolean isRequired() {return required;}public void setRequired(boolean required) {this.required = required;}public boolean isHidden() {return hidden;}public void setHidden(boolean hidden) {this.hidden = hidden;}}
}
三、配置完毕
在启动类增加注解开起swagger:@springfox.documentation.oas.annotations.EnableOpenApi
此时控制台报错
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is nullat 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.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:473) ~[na:na]at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na]at java.base/java.util.TimSort.sort(TimSort.java:220) ~[na:na]at java.base/java.util.Arrays.sort(Arrays.java:1307) ~[na:na]at java.base/java.util.ArrayList.sort(ArrayList.java:1721) ~[na:na]at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392) ~[na:na]at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[na:na]...........
原因是:主要出现在Spring Boot 2.6及以后,只要是Spring Boot 2.6引入的新PathPatternParser导致的。
四、解决方案
spring官方提及此issue:because "this.condition" is null · Issue #28794 · spring-projects/spring-boot · GitHub
但尚未解决,issue已关闭。
springfox社区活跃,已有大神解决此问题:Spring 5.3/Spring Boot 2.4 support · Issue #3462 · springfox/springfox · GitHub
适合的方案如下
1.Path匹配策略切换回ant_path_matcher(大多情况此方案可解决)
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
2.若还是不能解决,添加如下配置
@Bean
ic WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {List<ExposableEndpoint<?>> allEndpoints = new ArrayList();Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();allEndpoints.addAll(webEndpoints);allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());String basePath = webEndpointProperties.getBasePath();EndpointMapping endpointMapping = new EndpointMapping(basePath);boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
涉及依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId><version>2.7.14</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator-autoconfigure</artifactId><version>2.7.14</version><scope>compile</scope>
</dependency>
配置完重启服务问题解决!
彩蛋
swagger3与springboot完整的集成方案已上架,欢迎查收:彩蛋~