构建时从注解生成swagger.json及Swagger Akka HTTP技术问题咨询
我之前在Scala项目里用Swagger Akka HTTP时也碰到过一模一样的问题——既要兼容GraalVM原生镜像,又不想被多余的Jackson依赖拖累,分享几个亲测有效的方案:
1. 消除运行时反射,适配GraalVM原生镜像
Swagger Akka HTTP默认靠运行时反射扫描你的路由和模型类来生成swagger.json,这恰好是GraalVM原生镜像的大忌——原生镜像构建时会做静态分析,没被显式引用的反射目标会被裁剪掉,导致运行时崩溃。解决思路有两个:
方案A:编译时代码生成替代反射
放弃自动扫描,手动构建Swagger规范或者用编译期宏来生成所需的Swagger对象。这样完全绕开运行时反射,GraalVM就能完美兼容。
比如你可以直接用Swagger的Java API手动定义接口规范,再用Circe序列化:
import io.swagger.models.{Info, Path, Swagger, Operation} import io.swagger.models.parameters.QueryParameter import io.circe._ import io.circe.syntax._ // 手动构建整个Swagger文档结构 val mySwaggerSpec = new Swagger() .info(new Info().title("我的Scala API").version("v1.0.0").description("基于Akka HTTP的服务")) .path("/users/{userId}", new Path() .get(new Operation() .summary("获取用户详情") .addParameter(new QueryParameter().name("includePosts").type("boolean").defaultValue("false")) ) ) // 用Circe序列化为JSON字符串 val swaggerJson: String = mySwaggerSpec.asJson.noSpaces
之后你只需要把这个JSON字符串通过Akka HTTP暴露成/swagger.json端点就行,完全不需要Swagger Akka HTTP的自动扫描功能。
方案B:给GraalVM添加反射配置
如果你不想放弃自动扫描,可以用GraalVM的native-image-agent生成反射配置文件,告诉原生镜像哪些类需要被反射访问:
- 用JVM模式运行你的应用,触发Swagger生成的逻辑(比如调用一次
/swagger.json接口) - 运行时添加agent参数:
-agentlib:native-image-agent=config-output-dir=./graal-config - 执行完成后,
./graal-config目录下会生成reflect-config.json等配置文件 - 构建原生镜像时指定这些配置:
native-image -H:ConfigurationFileDirectories=./graal-config ...
不过这个方案比较繁琐,而且如果你的API端点和模型很多,配置文件会变得很大,后期维护成本高,更推荐方案A。
2. 移除冗余的Jackson依赖
Swagger Akka HTTP默认绑定了Jackson作为JSON处理库,而你已经在用Circe和Jaws,完全可以把Jackson相关依赖排除掉,再替换成Circe的序列化逻辑:
第一步:排除Jackson依赖
在sbt里修改依赖声明,排除所有Jackson相关模块:
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "1.0.6" excludeAll( ExclusionRule("com.fasterxml.jackson.core"), ExclusionRule("com.fasterxml.jackson.module"), ExclusionRule("com.fasterxml.jackson.datatype") )
Maven用户可以在依赖里添加<exclusions>节点做同样的操作。
第二步:替换为Circe序列化
Swagger Akka HTTP允许自定义JSON序列化器,你可以实现一个基于Circe的SwaggerSerializers:
import com.github.swaggerakkahttp.SwaggerSerializers import io.circe.{Encoder, Printer} import io.circe.generic.semiauto._ import io.swagger.models.Swagger // 为Swagger模型生成Circe编码器 implicit val swaggerEncoder: Encoder[Swagger] = deriveEncoder[Swagger] // 自定义Swagger序列化器 class CirceSwaggerSerializers extends SwaggerSerializers { override def toJson(value: AnyRef): String = { value match { case swagger: Swagger => swagger.asJson.printWith(Printer.noSpaces) case _ => super.toJson(value) // fallback for other types if needed } } }
然后在Swagger Akka HTTP的配置中指定使用这个自定义序列化器即可。
内容的提问来源于stack exchange,提问作者Mateusz Kubuszok

