Java 8 Fat-JAR构建后无法读取JPEG2000图片的解决咨询
我需要构建面向非开发者的、包含所有依赖的Java 8可执行JAR,核心功能是通过JavaFX的ImageView组件展示PDF内容,部分PDF内嵌JPEG2000格式图片,因此在pom.xml中添加了如下依赖:
<dependency> <groupId>com.github.jai-imageio</groupId> <artifactId>jai-imageio-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.github.jai-imageio</groupId> <artifactId>jai-imageio-jpeg2000</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>jbig2-imageio</artifactId> <version>3.0.0</version> </dependency>
本地.m2仓库中的类结构如附图所示,在IntelliJ 2024.2.4社区版中运行程序时,所有图片(含JPEG2000)均可正常显示。
但通过IntelliJ执行pom.xml的clean和package命令生成Fat-JAR后,发现JAR内com.github.jai-imageio包的路径与本地.m2不同。运行该可执行JAR并加载含JPEG2000图片的PDF时,抛出如下错误:
11/12/2024-09:10:42 ERROR PDFStreamEngine(935) - Cannot read JPEG2000 image: Java Advanced Imaging (JAI) Image I/O Tools are not installed org.apache.pdfbox.filter.MissingImageReaderException: Cannot read JPEG2000 image: Java Advanced Imaging (JAI) Image I/O Tools are not installed at org.apache.pdfbox.filter.Filter.findImageReader(Filter.java:172) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.filter.JPXFilter.readJPX(JPXFilter.java:123) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.filter.JPXFilter.decode(JPXFilter.java:72) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.filter.Filter.decode(Filter.java:254) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.cos.COSInputStream.create(COSInputStream.java:73) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.cos.COSStream.createInputStream(COSStream.java:172) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.common.PDStream.createInputStream(PDStream.java:193) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject.createInputStream(PDImageXObject.java:895) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.graphics.image.SampledImageReader.from8bit(SampledImageReader.java:469) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.graphics.image.SampledImageReader.getRGBImage(SampledImageReader.java:217) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject.getImage(PDImageXObject.java:477) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject.getImage(PDImageXObject.java:438) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.rendering.PageDrawer.drawImage(PageDrawer.java:1248) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.contentstream.operator.graphics.DrawObject.process(DrawObject.java:74) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.contentstream.PDFStreamEngine.processOperator(PDFStreamEngine.java:893) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.contentstream.PDFStreamEngine.processStreamOperators(PDFStreamEngine.java:531) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.contentstream.PDFStreamEngine.processStream(PDFStreamEngine.java:506) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.contentstream.PDFStreamEngine.processPage(PDFStreamEngine.java:153) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.rendering.PageDrawer.drawPage(PageDrawer.java:286) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.rendering.PDFRenderer.renderImage(PDFRenderer.java:330) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.rendering.PDFRenderer.renderImage(PDFRenderer.java:247) ~[CoverSheetCreatorFX_new.jar:?] at org.apache.pdfbox.rendering.PDFRenderer.renderImageWithDPI(PDFRenderer.java:219) ~[CoverSheetCreatorFX_new.jar:?]
当前开发环境为Java 8,pom.xml的build配置如下:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass>CoverSheetCreatorFX</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <appendAssemblyId>false</appendAssemblyId> </configuration> </execution> </executions> </plugin> </plugins> </build>
我推测pom.xml构建过程导致路径异常是问题根源,若该推测正确,如何修改pom.xml或其他配置解决问题?若推测错误,下一步该如何排查?
一、路径异常推测的验证与修复
你的推测大概率正确,maven-assembly-plugin打包fat jar时,仅简单合并类文件,容易丢失JAI ImageIO依赖的SPI配置文件(META-INF/services/javax.imageio.spi.ImageReaderSpi),或导致文件冲突,进而让PDFBox无法找到JPEG2000的解析器。
修改方案1:替换为maven-shade-plugin打包
maven-shade-plugin能更好处理SPI配置合并、依赖冲突等问题,是打包fat jar的更优选择:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <!-- 合并所有META-INF/services下的SPI配置文件 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> <!-- 指定可执行JAR的主类 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>CoverSheetCreatorFX</mainClass> </transformer> </transformers> <!-- 排除签名文件,避免打包时因重复签名报错 --> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> </build>
替换后执行mvn clean package,生成的fat jar会正确合并SPI配置,确保PDFBox能找到JPEG2000的ImageReader。
修改方案2:自定义assembly描述符(不推荐,较繁琐)
若坚持使用maven-assembly-plugin,需自定义描述符保留SPI配置:
- 在项目根目录创建
src/main/assembly/assembly.xml:
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> <id>jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <outputDirectory>/</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <unpack>true</unpack> <scope>runtime</scope> </dependencySet> </dependencySets> <fileSets> <fileSet> <directory>${project.build.outputDirectory}</directory> <outputDirectory>/</outputDirectory> </fileSet> </fileSets> <containerDescriptorHandlers> <!-- 合并META-INF/services下的配置文件 --> <containerDescriptorHandler implementation="org.apache.maven.plugins.assembly.containerdescriptorhandler.ServicesHandler"/> </containerDescriptorHandlers> </assembly>
- 修改pom.xml中的assembly-plugin配置,指定自定义描述符:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass>CoverSheetCreatorFX</mainClass> </manifest> </archive> <descriptors> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> <appendAssemblyId>false</appendAssemblyId> </configuration> </execution> </executions> </plugin>
二、若推测错误的排查步骤
如果修改打包插件后问题依旧,按以下步骤排查:
- 检查fat jar文件结构:解压生成的JAR,确认
com/github/jai-imageio路径下的类是否存在,同时查看META-INF/services/javax.imageio.spi.ImageReaderSpi文件中是否包含com.github.jaiimageio.impl.plugins.jpeg2000.JPEG2000ImageReaderSpi条目。 - 手动验证SPI加载:在项目中添加调试代码,打印所有可用的ImageReader,确认JPEG2000解析器是否被加载:
调用该方法后运行fat jar,查看输出是否包含JPEG2000相关的Reader类。import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import java.util.Iterator; public class DebugSPI { public static void printImageReaders() { IIORegistry registry = IIORegistry.getDefaultInstance(); Iterator<ImageReaderSpi> readers = registry.getServiceProviders(ImageReaderSpi.class, false); while (readers.hasNext()) { ImageReaderSpi spi = readers.next(); System.out.println("Loaded ImageReader: " + spi.getPluginClassName()); } } } - 排查依赖冲突:执行
mvn dependency:tree,查看是否有其他依赖覆盖了jai-imageio的类,或存在版本冲突。 - 验证Java 8兼容性:尝试更换jai-imageio的版本(比如jai-imageio-core:1.3.1),确认版本与Java 8完全兼容。
内容的提问来源于stack exchange,提问作者N C

