2023-08-08
实战设计
0

目录

源码分析
查找切入点
切入方式的选择
代码实现
实现效果
总结

由于对 SpringBoot 的版本升级,从 knife4j-spring-boot-starter 3.0.3 版本迭代到 knife4j-openapi2-spring-boot-starter 4.2.0 版本,其中 host 与 basePath 参数不能动态修改的问题,结合源码进行扩展。

源码分析

从页面的展示现象上与代码的配置上结合分析,两个版本之间的文档属性 host 与 basePath 应该为接口内部自动处理。 当我配置域名后,host、basepath 不能检测变更,导致接口调用全部失败。

基于以上问题,我决定找到源码中对应的点进行扩展,将 host、basepath 也作为动态可配置的参数。

查找切入点

首先通过 F12 doc.html 文档页面,发现主体数据是通过 /V2/docs-api 接口返回数据进行页面填充,知道了接口是什么,接下来分析接口在源码的哪个类中。

image.png

思考

怎么快速定位到源码中的接口?

结合学习过的源码知识,我此时想到了通过 SpringMVC 的执行流程源码来定位,打开 DispatcherServlet #doDispatch 方法,在处理器执行链那一行打个断点

image.png

刷新 doc.html 页面,看断点进来的接口有哪些,几次过后就发现了 /V2/docs-api 接口的调用

image.png

通过控制台查看 mappedHandler 对象内部的 handler 属性,同时能查看到处理器的实现类,点击 Navigate 可定位到处理器的实现类

image.png

找到接口的出处 Swagger2ControllerWebMvc类,查看接口,发现其内部获取 Swagger 对象后,单独设置了 host 与 basePath 属性,到了此时,已经定位了接口在源码中的位置,逻辑也已经完全清晰,接下来开始思考如何改造才能动态扩展出 host 与 basePath 属性?

image.png

切入方式的选择

思考

怎么扩展源码中Controller接口返回的数据?

对于 Controller 接口的处理,切入点还是比较多的,例如 HandlerInterceptor、ResponseBodyAdvice。

不过由于处理的是Jar包中的 Controller,它在加载到Spring容器的时候,方法的 HandlerInterceptor 已经确认,所以只能使用 ResponseBodyAdvice 方式。

代码实现

定义动态的属性配置,通过 application.yaml 隔离

yml
# dev knife4j: base-path: / host: localhost:${server.port}
yml
# prod knife4j: base-path: /yonger host: liushigong.cn

声明 @RestControllerAdvice 的类,实现 ResponseBodyAdvice 接口

java
@RestControllerAdvice public class Knife4jResponseAdvice implements ResponseBodyAdvice<Json> { @Value("${knife4j.base-path}") private String basePath; @Value("${knife4j.host}") private String host; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.getDeclaringClass().equals(Swagger2ControllerWebMvc.class); } @SneakyThrows @Override public Json beforeBodyWrite(Json body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body != null) { // 重写host 和 basePath Swagger swagger = JSONObject.parseObject(body.value(), Swagger.class); swagger.setHost(host); swagger.setBasePath(basePath); return new Json(JSONObject.toJSONString(swagger)); } return null; } }

思路:

  • 实现 ResponseBodyAdvice 接口,由于 /V2/docs-api 接口返回的是 Json 对象,所以泛型是 Json
  • 声明了动态的 host 、 basePath 属性进行注入
  • 调用 #supports 方法校验是否需要拦截,如果处理类是 Swagger2ControllerWebMvc.class 方法返回true
  • #supports为true后,会调用 #beforeBodyWrite 方法,内部将Json中返回的对象值转换为 Swagger对象,填充动态配置的 host、basePath 属性。

实现效果

image.png

总结

在平常遇到问题时,会去想如何去解决,当有一定的技术深度,掌握部分源码以后,遇到此类问题,脑海中会立马构思出一套思路,排查此类问题就会轻而易举,这就体现出了学习源码的价值。解决问题最重要的就是思路,提高深度就是拓展思路最好的方式!

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!