• Java云原生(Spring Native)开发初体验报告


    不愿关注的朋友,请访问:https://baofeidyz.com/javaspringnative

    背景

    前段时间在考虑做一款小工具,功能非常简单,调用多个HTTP接口,分析处理返回的数据,生成Excel文件即可。
    为了尽量的让这个工具的实用性更高,我首先想到Java的云原生开发方案,直接构建为可执行文件,不需要使用的人再去安装jre运行环境,或者是带着庞大的jre文件发出。再者,我也想试试Java的云原生方案到底好不好用。

    技术选型

    因为一直在使用Spring开发业务,所以我这次直接使用了Spring Native

    开发过程

    注:均基于 macOS

    开发环境安装

    Spring官网的文章写的非常好,直接参考官文就可以了。Spring官方文档
    我选择安装了sdk man,然后

    sdk install java 22.1.r11-nik
    
    • 1

    剩下的就是直接用spring native创建一个项目就可以了,这个非常简单。

    编译构建

    1. 如果你用了阿里云的maven仓库,请记得切换成apache的原库
    2. macOS默认mvn命令指定的jdk版本可能不是你新安装的GraalVM(像我本地就有七八个不同的JDK版本),会出现一些编译错误,那么你需要创建~/.mavenrc文件
    // 路径记得自己改成正确的
    export JAVA_HOME=/Users/baofeidyz/.sdkman/candidates/java/current
    
    • 1
    • 2

    报错集锦

    报错1

    gu install native-image
    Downloading: Component catalog from download.bell-sw.com
    Error: Unknown component: native-image
    
    • 1
    • 2
    • 3

    在高版本里面,这个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) 
    有省略部分堆栈
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个报错只有在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 { // ... }
    
    • 1
    • 2
    • 3

    我顺着这个例子,修改了一下代码

    @TypeHint(types = XXXDTO.class, typeNames = "com.baofeidyz.XXXDTO")  
    @SpringBootApplication  
    public class MySpringNativeApplication {
    // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    确实解决了这个jackson反序列化导致的错误,但我又遇到了新的错误。

    报错3 缺少字符集

    java.nio.charset.UnsupportedCharsetException: CP1252
            at java.nio.charset.Charset.forName(Charset.java:529)
            at org.apache.poi.util.LocaleUtil.(LocaleUtil.java:56)
            at org.apache.poi.ss.usermodel.DataFormatter.(DataFormatter.java:242)
            at org.apache.poi.ss.usermodel.DataFormatter.(DataFormatter.java:233) 
            at org.apache.poi.ss.formula.functions.TextFunction.(TextFunction.java:33) 
            at org.apache.poi.ss.formula.atp.AnalysisToolPak.createFunctionsMap(AnalysisToolPak.java:82)
            at org.apache.poi.ss.formula.atp.AnalysisToolPak.(AnalysisToolPak.java:47)
            at org.apache.poi.ss.formula.atp.AnalysisToolPak.(AnalysisToolPak.java:33) 
            at org.apache.poi.ss.formula.udf.AggregatingUDFFinder.(AggregatingUDFFinder.java:35)
            at java.lang.Class.ensureInitialized(DynamicHub.java:518) 
            at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:155) ~[na:na]
            at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:226) ~[na:na]
            at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:214) ~[na:na]
            // 有省略堆栈信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个看起来像是poi在操作的时候,遇到了一个chartset问题,spring有一个专门的仓库我在这个仓库里面找到了这个issue,我看到一个人也遇到了这个charset的问题,但是他是mysql的时候遇到的,他提到加了一个参数用于解决这个问题。
    紧接着,我又找到了几个issue:

    了解到说,需要在native-image编译的时候,增加一个AddAllCharsets参数
    但是我是使用的spring native的maven插件,我的构建命令是mvn -Pnative,我并没有直接使用native-image,我不知道怎么增加这个参数才是对的。
    此外,我还看了两份文档:

    终于,我知道如何在Spring Native中增加这个AddAllCharsets参数了 😄

    @TypeHint(types = XXXDTO.class, typeNames = "com.baofeidyz.XXXDTO")  
    // 👇 下面这行代码就解决了我的问题
    @NativeHint(options = "-H:+AddAllCharsets")  
    @SpringBootApplication  
    public class MySpringNativeApplication {  
    // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最佳实践就是加一个@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)
            // 有省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我猜测应该是因为资源没有引入导致的,所以我理解应该还是应该用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 {
    // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    报错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.(XSSFWorkbook.java:232) 
            at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:226) 
            at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:214) 
            // 有省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后我在GraalVM的issue清单中找到了这个issue:Getting following error while generating excel file using native image. · Issue #1929 · oracle/graal · GitHub 基本上错误信息就是一致的。
    我试了一下,不行。

    我感觉POI和GraalVM配合坑有点多,我不打算一个一个找了,我尝试去找一些大而全的文章,于是,我找到了这篇文章:Graalvm使用采坑 - 行万里路才能回到内心深处,读万卷书才能看得清皓月繁星

    可惜还是失败了,我感觉算了吧,POI和GraalVM的路还很长哦…

    一些思考

    1. 对于这种POI组件的问题,处理起来确实很难。我查了GraalVM的ISSUE清单,5.0之前的POI确实是有一些问题,但与此同时GraalVM可能需要大量的构建参数去配置,然后Spring Native又在其中转了一次。也就是说,我需要知道这个问题不是POI导致的,然后去看native-image对应的解决方案(配置参数)是什么,然后再转成Spring Native的hints。如果我不能确定这个问题是不是POI导致的,我连提ISSUE给谁都不清楚,如果自己尝试去排查编译问题,这就会进入一个更深的坑。
    2. 我建议如果Spring Native example下找不到对应的例子之前,不要去趟这个浑水,整个开发过程非常阻塞,问题很难说真正的解决完。
    3. Java云原生的路确实很难走,不管是GraalVM去适配组件,还是组件去适配GraalVM,整个适配时间会拉得很长很长。
    4. GraalVM编译时间很长,我的项目并不大,4核8线程,16G内存,但是每次都需要大概3分钟左右,并且编译的时候,CPU使用率一直都很高。

    后续

    我后来用GO重写了整个程序,这是我第一次用GO,从0开始学,大概花了20个小时就写完了,并且我还用了协程并发,处理了一些线程安全的问题。相比之下,Spring Native至少花了我四倍的时间,并且没有做完。
    GO编译速度非常快,我交叉编译linux或windows也才10s,CPU使用率也不高,相比之下,GO做这类小工具的云原生开发真的是非常好用。

    所以我在怀疑,Java云原生是否真的有意义?希望十年后,能被打脸。

    以上就是我Java云原生的初步体验报告,如果对你有一点点帮助,希望可以给我的文章点个赞和收藏。

  • 相关阅读:
    登录远程SQLServer
    社区分享|中南民族大学基于JumpServer构建规范、便利的运维安全体系
    计算机毕业设计之java+javaweb的影院管理系统-电影院管理系统
    CocosCreator3.8研究笔记(十一)CocosCreator Prefab(预制件)理解
    GBASE 8s中onshutdown 脚本的用法
    JWT 使用入门(三)请求流程
    k8s 容忍和污点
    Seeking Alpha From Market Participants‘ Inoformation Acquisition Actions
    Pillow:Python的图像处理库(安装与使用教程)
    【Matlab】数据插值
  • 原文地址:https://blog.csdn.net/baofeidyz/article/details/126244318