背景
前段时间在考虑做一款小工具,功能非常简单,调用多个HTTP接口,分析处理返回的数据,生成Excel文件即可。 为了尽量的让这个工具的实用性更高,我首先想到Java的云原生开发方案,直接构建为可执行文件,不需要使用的人再去安装jre运行环境,或者是带着庞大的jre文件发出。再者,我也想试试Java的云原生方案到底好不好用。
技术选型
因为一直在使用Spring开发业务,所以我这次直接使用了Spring Native
开发过程
注:均基于 macOS
开发环境安装
Spring官网的文章写的非常好,直接参考官文就可以了。Spring官方文档 我选择安装了sdk man,然后
sdk install java 22.1.r11-nik
剩下的就是直接用spring native创建一个项目就可以了,这个非常简单。
编译构建
如果你用了阿里云的maven仓库,请记得切换成apache的原库
macOS默认mvn命令指定的jdk版本可能不是你新安装的GraalVM(像我本地就有七八个不同的JDK版本),会出现一些编译错误,那么你需要创建
~/.mavenrc
文件
// 路径记得自己改成正确的
export JAVA_HOME=/Users/baofeidyz/.sdkman/candidates/java/current
报错集锦
报错1
gu install native-image
Downloading: Component catalog from download.bell-sw.com
Error: Unknown component: native-image
在高版本里面,这个native-image
好像不需要手动安装了(也有可能是我之前安装过)Install fails in 19.2.0 with gu · Issue #1665 · oracle/graal · GitHub 然后我直接运行了一下native-image
发现已经有了
报错2 反射错误
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.baofeidyz.XXXDTO` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: java.util.ArrayList[0])
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1415)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4650)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2856)
有省略部分堆栈
这个报错只有在GraalVM
下运行时才会出现,我最开始以为是因为我用了lombok
然后导致的编译问题,同时我也有注意到,Spring Native
本身是直接带了lombok
的,我觉得不太可能吧。 但是我还是尝试去掉了lombok
,当然并没有什么用。
然后我又开始在网上寻找一些蛛丝马迹,看看有没有其他人也遇到了类似的问题。但是Java Native问题的搜索真的是非常难找,因为这类问题有可能因为其他的原因也会出现,并且由于Spring Native使用人非常的少,你很难在Google的结果中找到真正的答案。我也尝试使用site:github.com
再去Google搜索,仍没有找到答案。 我甚至都想去提issue了 😂 GitHub - graalvm/native-build-tools: Native-image plugins for various build tools
我后来咨询了一位做过Spring Native开发的朋友,他告诉我,可能是因为反射导致的,需要增加配置。 我顺着这位朋友的思路,去看了一下反射配置的问题,正好在这篇文章中给出了一个示例
@TypeHint(types = Data.class, typeNames = "com.example.webclient.Data$SuperHero")
@SpringBootApplication
public class SampleApplication { // ... }
我顺着这个例子,修改了一下代码
@TypeHint(types = XXXDTO.class, typeNames = "com.baofeidyz.XXXDTO")
@SpringBootApplication
public class MySpringNativeApplication {
// ...
}
确实解决了这个jackson
反序列化导致的错误,但我又遇到了新的错误。
报错3 缺少字符集
java.nio.charset.UnsupportedCharsetException: CP1252
at java.nio.charset.Charset.forName(Charset.java:529)
at org.apache.poi.util.LocaleUtil.<clinit>(LocaleUtil.java:56)
at org.apache.poi.ss.usermodel.DataFormatter.<init>(DataFormatter.java:242)
at org.apache.poi.ss.usermodel.DataFormatter.<init>(DataFormatter.java:233)
at org.apache.poi.ss.formula.functions.TextFunction.<clinit>(TextFunction.java:33)
at org.apache.poi.ss.formula.atp.AnalysisToolPak.createFunctionsMap(AnalysisToolPak.java:82)
at org.apache.poi.ss.formula.atp.AnalysisToolPak.<init>(AnalysisToolPak.java:47)
at org.apache.poi.ss.formula.atp.AnalysisToolPak.<clinit>(AnalysisToolPak.java:33)
at org.apache.poi.ss.formula.udf.AggregatingUDFFinder.<clinit>(AggregatingUDFFinder.java:35)
at java.lang.Class.ensureInitialized(DynamicHub.java:518)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:155) ~[na:na]
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:226) ~[na:na]
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:214) ~[na:na]
// 有省略堆栈信息
这个看起来像是poi在操作的时候,遇到了一个chartset
问题,spring有一个专门的仓库我在这个仓库里面找到了这个issue,我看到一个人也遇到了这个charset
的问题,但是他是mysql
的时候遇到的,他提到加了一个参数用于解决这个问题。 紧接着,我又找到了几个issue:
了解到说,需要在native-image
编译的时候,增加一个AddAllCharsets
参数 但是我是使用的spring native的maven插件,我的构建命令是mvn -Pnative
,我并没有直接使用native-image
,我不知道怎么增加这个参数才是对的。 此外,我还看了两份文档:
关于这个如何查看堆信息之类的:GraalVM Dashboard
关于
native-image
这个命令如何使用的:https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_A4.pdf 然后我又去查了一下Spring Native自己维护的一个hints项目:spring-native/spring-native-configuration/src/main/java at main · spring-projects-experimental/spring-native · GitHub 然后又去研究了一下Spring Native的脚本:spring-native/compileWithMaven.sh at main · spring-projects-experimental/spring-native · GitHub
终于,我知道如何在Spring Native中增加这个AddAllCharsets
参数了 😄
@TypeHint(types = XXXDTO.class, typeNames = "com.baofeidyz.XXXDTO")
// 👇 下面这行代码就解决了我的问题
@NativeHint(options = "-H:+AddAllCharsets")
@SpringBootApplication
public class MySpringNativeApplication {
// ...
}
最佳实践就是加一个@NativeHint(options = "-H:+AddAllCharsets")
当然不可能这么顺利的,很快我又遇到了新的报错
错误4 POI的resource文件未加载
org.apache.xmlbeans.SchemaTypeLoaderException: XML-BEANS compiled schema: Could not locate compiled schema resource org/apache/poi/schemas/ooxml/system/ooxml/index.xsb (org.apache.poi.schemas.ooxml.system.ooxml.index) - code 0
at org.apache.xmlbeans.impl.schema.XsbReader.<init>(XsbReader.java:63)
at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.initFromHeader(SchemaTypeSystemImpl.java:235)
at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.<init>(SchemaTypeSystemImpl.java:201)
at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.<init>(TypeSystemHolder.java:9)
at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.<clinit>(TypeSystemHolder.java:6)
at org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook.<clinit>(CTWorkbook.java:22)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:475)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:232)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:226)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:214)
// 有省略
我猜测应该是因为资源没有引入导致的,所以我理解应该还是应该用spring native提供的hint去实现。我参考这个代码块 增加了一个@ResourceHint
@TypeHint(types = XXXDTO.class, typeNames = "com.baofeidyz.XXXDTO")
@ResourceHint(patterns = {"(^/|[a-zA-Z])*/.+(/$)?.xsb"})
@NativeHint(options = "-H:+AddAllCharsets")
@SpringBootApplication
public class MySpringNativeApplication {
// ...
}
报错5
java.lang.ClassCastException: org.apache.xmlbeans.impl.values.XmlComplexContentImpl cannot be cast to org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook
at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:475)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:232)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:226)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:214)
// 有省略
然后我在GraalVM的issue清单中找到了这个issue:Getting following error while generating excel file using native image. · Issue #1929 · oracle/graal · GitHub 基本上错误信息就是一致的。 我试了一下,不行。
我感觉POI和GraalVM配合坑有点多,我不打算一个一个找了,我尝试去找一些大而全的文章,于是,我找到了这篇文章:Graalvm使用采坑 - 行万里路才能回到内心深处,读万卷书才能看得清皓月繁星
可惜还是失败了,我感觉算了吧,POI和GraalVM的路还很长哦...
一些思考
对于这种POI组件的问题,处理起来确实很难。我查了GraalVM的ISSUE清单,5.0之前的POI确实是有一些问题,但与此同时GraalVM可能需要大量的构建参数去配置,然后Spring Native又在其中转了一次。也就是说,我需要知道这个问题不是POI导致的,然后去看native-image对应的解决方案(配置参数)是什么,然后再转成Spring Native的hints。如果我不能确定这个问题是不是POI导致的,我连提ISSUE给谁都不清楚,如果自己尝试去排查编译问题,这就会进入一个更深的坑。
我建议如果Spring Native example下找不到对应的例子之前,不要去趟这个浑水,整个开发过程非常阻塞,问题很难说真正的解决完。
Java云原生的路确实很难走,不管是GraalVM去适配组件,还是组件去适配GraalVM,整个适配时间会拉得很长很长。
GraalVM编译时间很长,我的项目并不大,4核8线程,16G内存,但是每次都需要大概3分钟左右,并且编译的时候,CPU使用率一直都很高。
后续
我后来用GO重写了整个程序,这是我第一次用GO,从0开始学,大概花了20个小时就写完了,并且我还用了协程并发,处理了一些线程安全的问题。相比之下,Spring Native至少花了我四倍的时间,并且没有做完。 GO编译速度非常快,我交叉编译linux或windows也才10s,CPU使用率也不高,相比之下,GO做这类小工具的云原生开发真的是非常好用。
所以我在怀疑,Java云原生是否真的有意义?希望十年后,能被打脸。
以上就是我Java云原生的初步体验报告,如果对你有一点点帮助,希望可以给我的文章点个赞和收藏。