起因:公司一个桌面客户端工具,原本使用.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.Wang原创文章,转载无需和我联系,但请注明来自lokie博客http://lokie.wang