Graalvm使用采坑

起因:公司一个桌面客户端工具,原本使用.net开发一直依赖于Office OLE来进行word 到PDF的导出,文档格式互转一直是一块比较麻烦的功能,对于word转PDF一直没有比较的好的方案。要么就是使用类似Apose.word或者Apose.PDF这样的商业组件,要么就是依赖于微软的Office OLE自动化方案。但是这样做的缺点非常的明显就是依赖于Office组件,客户的电脑上必须有office被安装,而且需要有合适的版本的。这非常困扰我们。最近有客户希望去掉这个依赖,因此又做了一番调研,方向无非是成熟的开源组件。

类似NPOI、ITextSharp都发现没有该功能,当然也有思路,就是采用类似OpenXML sdk的方式读取Office文件,根据格式采用itextSharp等组件去自行转换,但是工作量明显比较高,近期的任务比较多是没有时间去完成的。突然发现似乎Java那有可以用的方法,POI似乎结合了IText开发了相应的功能,由于对apache的信赖感,决定试下看看,如果可行再解决Java和.net互操作的问题。

废话不多数,用maven建立一个控制台程序,在pom.xml中输入加如下依赖:

 <dependency>
  <groupId>fr.opensagres.xdocreport</groupId> <artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
  <version>1.0.6</version>
  </dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>

接下来是在main函数中试下:

String docFile = args[0];
String pdfFile = args[1];
final String ttf = args[2];
InputStream is = new FileInputStream(new File(docFile));
OutputStream os = new FileOutputStream(new File(pdfFile));
XWPFDocument xwpfDocument = new XWPFDocument(is);
PdfOptions options = PdfOptions.create();
PdfConverter.getInstance().convert(xwpfDocument, os, options);

编译通过后运行:发现可以将word文件转换为PDF,但是会丢失中文信息。于是查阅文档加入如下代码:

    // 中文字体处理
            options.fontProvider(new IFontProvider() {
                @Override
                public Font getFont(String familyName, String encoding, float size, int style, java.awt.Color color) {
                    try {

                        BaseFont bfChinese = BaseFont.createFont(ttf, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
                        Font fontChinese = new Font(bfChinese, size, style, color);
                        if (familyName != null) {
                            fontChinese.setFamily(familyName);
                        }
                        return fontChinese;

                    } catch (Exception e) {
                        e.printStackTrace();
                        return null;
                    }
                }
            });

这里注意的是要有一个中文ttf文件,这里选用的是simsun.ttf.

于是非常顺利的完成了这个功能,整个pom如下的build配置如下:

    
    <build>
<plugins>
  <plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
      <instructions>
        <Import-Package>*</Import-Package>
        <Export-Package></Export-Package>
        <Include-Resource>
          @src/main/resource/simfang.ttf
        </Include-Resource>
      </instructions>
      <manifestLocation>${project.basedir}/META-INF</manifestLocation>
    </configuration>
  </plugin>
  <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
      <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
      <archive>
        <manifest>
          <mainClass>com.imacco.bidding.App</mainClass>
        </manifest>
      </archive>
    </configuration>
  </plugin>
</plugins>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
  <plugins>
    <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
    <plugin>
      <artifactId>maven-clean-plugin</artifactId>
      <version>3.1.0</version>
    </plugin>
    <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
    <plugin>
      <artifactId>maven-resources-plugin</artifactId>
      <version>3.0.2</version>
    </plugin>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.0</version>
    </plugin>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.1</version>
    </plugin>
    <plugin>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.0.2</version>
    </plugin>
    <plugin>
      <artifactId>maven-install-plugin</artifactId>
      <version>2.5.2</version>
    </plugin>
    <plugin>
      <artifactId>maven-deploy-plugin</artifactId>
      <version>2.8.2</version>
    </plugin>
    <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
    <plugin>
      <artifactId>maven-site-plugin</artifactId>
      <version>3.7.1</version>
    </plugin>
    <plugin>
      <artifactId>maven-project-info-reports-plugin</artifactId>
      <version>3.0.0</version>
    </plugin>
  </plugins>
</pluginManagement>
<finalName>pdfcreateor</finalName>
</build>

通过 mvn assembly:assembly 可以打包 flat jar包,然后通过
java -jar xx.jar word pdf ttf来运行,发现完美转换速度不慢。

那问题就到了.net和java的互操作了。方法1就是使用web 服务,不科学。方法2呢就是用命令行运行jar包,这就要求了用户依赖java环境。 有没有其他方法呢?当然有,就是今天的主角Graalvm这个神器,这个orcale在开发的下个JVM的。其主要特点就是AOT、多语言(Java、NodeJS llvm语言、Python、R)的多环境虚拟机,还有就是Native-Image。看下这个牛逼哄哄的东西的接受吧。

GraalVM是Java VM和基於HotSpot / OpenJDK的JDK,以Java實現。它支持其他編程語言和執行模式,例如用於快速啟動和低內存佔用的Java應用程序的提前編譯。第一個生產就緒版本GraalVM 19.0已於2019年5月發布。最新版本是GraalVM 20.0.0,於2020年2月可用. 好幸运刚好可用。

那就带着大家来采坑吧,主要是Native Image这一块。

首先下载个个平台的安装包,由于是要在windows下运行,我下载了graalvm-ce-java8-20.0.0.zip 解压后放入任意磁盘下
看下文件:

bin目录是主要的文件的

可以看的java下的常用命令和python node的执行文件。
下面安装native-image的安装。native-image在社区版是作为原生插件安装的

bin/gu install native-image 

就会从github下载native-image这个插件。

下面使用native-image命令产生原生的exe,很简单啊,准备之前的java文件。

native-image -jar d:\pdfcreateor-jar-with-dependencies.jar 

自以为应该可以了,看下输出:

在bin目录就会出来一个pdfcreateor-jar-with-dependencies.exe 既然是warning的话不要紧就可以执行就好了不管他

    pdfcreateor-jar-with-dependencies.exe  word pdf ttf 

如果可以执行很开心,但是当复制到其他window的机器下,并没有装这个graalvm和jvm时候傻眼了,说不行依赖于jvm,那这个native-image没用吗?

其实不是,查阅文档才值到,warring中说的那些反射才是元凶。由于二进制化了并包含了一个裁剪的jvm,所以常规的反射是不被支持的,如果这样会生产一个依赖jvm的exe,要支持反射必须进行配置:

-H:ReflectionConfigurationFiles

同时通过文档得知:

-H:JNIConfigurationFiles:用来配置jni

-H:ResourceConfigurationFiles:用来配置resoure

那这个配置文件如何办呢难道手写吗?官方提供了自动生成这些文件的办法就是运行,tracking。

https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md

https://github.com/oracle/graal/blob/master/substratevm/CONFIGURE.md

/path/to/graalvm/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ 

最主要的就是以上命令,文档指出其实这是一个反复的过程,因为程序执行,并不是都能执行的每个点上,需要不断的合并。于是执行如下命令:

        native-image.cmd -jar d:\pdfcreateor-jar-with-dependencies.jar  -H:ReflectionConfigurationFiles=d:\reflect-config.json -H:JNIConfigurationFiles=d:\jni-config.json -H:ResourceConfigurationFiles=d:\resource-config.json
        

编译后在没有jvm环境的机器上能执行了,但是会报错,原因是一些jar包的缺少或者一些资源文件的缺失。

jar包的缺少可以通过在原始jar包中加入依赖,至于资源文件嵌入的缺少看报错去修复JNIConfigurationFiles的缺少,重新编译生成exe再测试。最终不出现这个错误了,但是又出现了新的问题就是java.io包不支持cp2025字符集,查找资料,在官方github中有类似iisue在windows下的问题的,需要增加-H:+AddAllCharsets 就是允许所有字符集,由于native-image命令参数太多,这里是说完的,多读文档吧。最后:

    native-image.cmd -jar d:\pdfcreateor-jar-with-dependencies.jar  -H:ReflectionConfigurationFiles=d:\reflect-config.json -H:JNIConfigurationFiles=d:\jni-config.json -H:ResourceConfigurationFiles=d:\resource-config.json -H:+AddAllCharsets
    

果然exe能脱离jvm运行。

PS:Graalvm目前在windows下依赖windows 7 sdk中的c++组件,如果么有就说找不到c++环境无法运行。在Linux下和mac下依赖于gcc和clang,这些不是问题。 至此编译出来的exe唯一依赖vc runtime 2010 x64,性能也非常不错。至于能不能静态编译,暂时没有调研。

当然也看到用这个来对服务端spring做优化的,可以改天试下或者其其他语言虚拟机特性的,必须java+py java+r java+js,潜力无限啊。

Lokie博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.1 搭建 © 2014-2018 lokie.wang 版权所有 ICP证:沪ICP备18016993号
  • 联系邮箱:kitche1985@hotmail.com