JAI供应商名称为空

14

我完成了旋转TIFF图像的应用程序编码,需要使用JAI来处理TIFF文件。

在Eclipse中工作时一切正常,但是每当我为库创建一个fat jar并实现它(按照http://fjep.sourceforge.net/fjeptutorial.html),当我执行java -jar Push.jar \path\to\dir时,它运行到压缩和保存部分时就停止了:

TIFFImageWriterSpi tiffspi = new TIFFImageWriterSpi();
ImageWriter writer = tiffspi.createWriterInstance();
//Iterator<ImageWriter> iter =  ImageIO.getImageWritersByFormatName("TIFF");
//ImageWriter writer = iter.next();

ImageWriteParam param2 = writer.getDefaultWriteParam();
param2.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

param2.setCompressionType("LZW");
param2.setCompressionQuality(0.0f);
File fOutputFile = workArea[i];
ImageOutputStream ios = ImageIO.createImageOutputStream(fOutputFile);
writer.setOutput(ios);

if (frontPage == 1)
{
     writer.write(null, new IIOImage(pg1, null, null), param2);
     writer.writeInsert(-1, new IIOImage(pg2, null, null), param2);
}
else if (frontPage == 2)
{
     writer.write(null, new IIOImage(pg2, null, null), param2);
     writer.writeInsert(-1, new IIOImage(pg1, null, null), param2);
}

remaining = remaining - 1;
    if (remaining > 0)
     System.out.println(remaining + " remaining.");
else
     System.out.println("Done.");

它在该部分的第一行引发错误,错误信息为:

 Exception in thread "main" java.lang.IllegalArgumentException: vendorName == null!
 ....rest of stack trace.
8个回答

34

由于我花费了相当长的时间来调试这个问题,尽管这个问题已经有些年头了,但我仍然想在此分享我的解决方案。Srikanth的第二个链接特别有帮助。

错误原因

JAI需要供应商名称才能获取一些深层次的内部信息,特别是javax.imageio.spi.IIOServiceProvider,它被许多(全部?)图像读取器用于低级IO。它不挑剔字符串是什么,但它不能为null。

ImageReaderSpi类并没有直接将厂商名称硬编码到其中,而是从sun.media.imageioimpl.common.PackageUtil.getVendor()中获取厂商名称。这反过来从jar文件的MANIFEST.MF中读取。通常情况下,您会与标准的jai-imageio包进行链接,因此会读取Sun的厂商信息。但是,由于你正在制作一个fat jar文件,你用自己的MANIFEST.MF替换了Sun的MANIFEST.MF,这导致了所需信息的缺失。

解决办法

在您的MANIFEST.MF文件中包含以下行:

Specification-Title: Java Advanced Imaging Image I/O Tools
Specification-Version: 1.1
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: com.sun.media.imageio
Implementation-Version: 1.1
Implementation-Vendor: Sun Microsystems, Inc.

每个属性的值可以是任何内容(我使用了我的特定应用程序/版本/公司),只要所有六个都定义了。

Maven

如果您正在使用maven的assembly插件创建fat jar文件,maven可以自动包含正确的版本号等信息。请在您的pom.xml中更新以下<archive>部分:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
            <manifestEntries>
                <Specification-Vendor>MyCompany</Specification-Vendor>
                <Implementation-Vendor>MyCompany</Implementation-Vendor>
            </manifestEntries>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>create-my-bundle</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

1
比“官方”接受的解决方案更好、更清晰!谢谢! - Funkwecker
1
也许是这样,但这个答案直到一个可行的解决方案“正式”被接受2年后才出现。我要把它换成这个答案,只因为第二个链接提供了与此处所写的解决方案相同但没有解释的解决方案,似乎已经被其他内容取代,所以现在不像当时那么有用。 - Robert
也许你可以看一下https://github.com/geoHeil/jai-packaging-problem,这不是同样的问题,即使我已经检查了清单条目,但是fat jar中的某些内容为空。 - Georg Heiler

5

我不得不使用这个ImageIO jar,它非常好用。你可以在这里找到它。


1
注意:此imageio jar已经修补,使用硬编码字符串来避免调用PackageUtil,后者尝试从清单中解析vendorName。 - eis

4

Quantum7的被接受的答案解释了问题的来源,在Maven部分提供了使用Maven Assembly插件生成包含依赖项的JAR文件的解决方案。如果您使用Maven Shade插件生成带有依赖项的JAR,则配置略有不同。您可以将以下内容添加到pom.xml中Shade插件的配置部分:

<configuration>
    <transformers>
        <transformer
                implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <manifestEntries>
                <Main-Class>com.conveyal.r5.R5Main</Main-Class>
                <Specification-Title>Java Advanced Imaging Image I/O Tools</Specification-Title>
                <Specification-Version>1.1</Specification-Version>
                <Specification-Vendor>Sun Microsystems, Inc.</Specification-Vendor>
                <Implementation-Title>com.sun.media.imageio</Implementation-Title>
                <Implementation-Version>1.1</Implementation-Version>
                <Implementation-Vendor>Sun Microsystems, Inc.</Implementation-Vendor>
                <Extension-Name>com.sun.media.imageio</Extension-Name>
            </manifestEntries>
        </transformer>
    </transformers>
</configuration>

2

免责声明

我遇到的问题略有不同,尝试运行编译后的jar文件时,我遇到了“ClassNotFound”错误。在研究过程中,我偶然发现了这个SO问题,所以对于其他按照我的轨迹寻找答案的人们,这里提供给你们。

解决ClassNotFound错误的可能方案

对于那些将来可能遇到此问题但其他方法似乎都无效的人们,您可以尝试使用Maven的Apache Shader插件。这里是一些关于它的更多信息。

我没有足够的经验来告诉您它的具体实现方法,但Apache Shader会将您项目中使用的所有依赖打包到最终的jar文件中,因此当构建项目时,所有的依赖项都会包含在META-INF文件夹中。这将增加jar文件的大小(取决于您项目中使用的库的数量),但也似乎修复了jar文件无法找到使用的外部库类的问题。

要使用Shader插件,请将以下内容添加到您的POM文件中。我将其包含在dependencies标签之后和properties标签之前。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <filters>
                            <filter>
                                <artifact>*:*</artifact>
                                <excludes>
                                    <exclude>META-INF/*.SF</exclude>
                                    <exclude>META-INF/*.DSA</exclude>
                                    <exclude>META-INF/*.RSA</exclude>
                                </excludes>
                            </filter>
                        </filters>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <manifestEntries>
                                    <Main-Class>com.package.name.className</Main-Class>
                                    <Build-Number>1</Build-Number>
                                </manifestEntries>
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意: 确保更改包和类名称以反映您项目的包和类名称。

其他有用的链接: 类似的stackoverflow问题


2

我遇到了同样的问题。Quantum7的答案解决了我的问题!然而,在加入manifestEntries Specification-Vendor和Implementation-Vendor之后,我的fat jar仍然执行失败,并出现以下异常。请注意,它是

version == null!

而不是

vendorName == null!

java.util.ServiceConfigurationError: javax.imageio.spi.ImageReaderSpi: Provider com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReaderSpi could not be instantiated
        at java.util.ServiceLoader.fail(Unknown Source)
        at java.util.ServiceLoader.access$100(Unknown Source)
        at java.util.ServiceLoader$LazyIterator.nextService(Unknown Source)
        at java.util.ServiceLoader$LazyIterator.next(Unknown Source)
        at java.util.ServiceLoader$1.next(Unknown Source)
        at javax.imageio.spi.IIORegistry.registerApplicationClasspathSpis(Unknown Source)
        at javax.imageio.spi.IIORegistry.<init>(Unknown Source)
        at javax.imageio.spi.IIORegistry.getDefaultInstance(Unknown Source)
        at javax.imageio.ImageIO.<clinit>(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: version == null!
        at javax.imageio.spi.IIOServiceProvider.<init>(Unknown Source)
        at javax.imageio.spi.ImageReaderWriterSpi.<init>(Unknown Source)
        at javax.imageio.spi.ImageReaderSpi.<init>(Unknown Source)
        at com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReaderSpi.<init>(CLibJPEGImageReaderSpi.java:80)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at java.lang.Class.newInstance(Unknown Source)
        ... 14 more

将manifestEntries放在下面解决了问题。
<manifestEntries>
    <Specification-Title>Java Advanced Imaging Image I/O Tools</Specification-Title>
    <Specification-Version>1.1</Specification-Version>
    <Specification-Vendor>Sun Microsystems, Inc.</Specification-Vendor>
    <Implementation-Title>com.sun.media.imageio</Implementation-Title>
    <Implementation-Version>1.1</Implementation-Version>
    <Implementation-Vendor>Sun Microsystems, Inc.</Implementation-Vendor>
</manifestEntries>

在运行fat jar时,我确保以下代码中的三个值都不为null。

import com.sun.media.imageioimpl.common.PackageUtil;

public class ManifestCheck {
    public static void main(String[] args) {
        System.out.println(PackageUtil.getVendor());
        System.out.println(PackageUtil.getVersion());
        System.out.println(PackageUtil.getSpecificationTitle());
    }
}

我的Maven配置中的“addDefaultImplementationEntries”部分根据项目属性定义了标题和版本。如果您想将它们设置为Sun的信息(我不建议这样做;自定义条目可以正常工作),那么您应该省略<manifest>块。 - Quantum7

1

在苦苦寻找解决方案后,我也遇到了同样的错误。最终我找到了这个解决方法并成功解决了问题。

需要在代码中手动给出venderName:

import com.sun.media.imageioimpl.common.PackageUtil;

    
    try {
        MainClass.afVenderNames(PackageUtil.class, "afreen", "afreen", "afreen");
    } catch (NoSuchFieldException e1) {
        e1.printStackTrace();
    } catch (SecurityException e1) {
        e1.printStackTrace();
    } catch (IllegalArgumentException e1) {
        e1.printStackTrace();
    } catch (IllegalAccessException e1) {
        e1.printStackTrace();
    }
    System.out.println(PackageUtil.getVendor());
    System.out.println(PackageUtil.getVersion());
    System.out.println(PackageUtil.getSpecificationTitle());
    
    
    
    public static void afVenderNames(Class<?> PackageUtil, String vendor, String version, String specTitle)
        throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    Field vendorField = PackageUtil.getDeclaredField("vendor");
    vendorField.setAccessible(true);
    vendorField.set(null, vendor);

    Field versionField = PackageUtil.getDeclaredField("version");
    versionField.setAccessible(true);
    versionField.set(null, version);

    Field specTitleField = PackageUtil.getDeclaredField("specTitle");
    specTitleField.setAccessible(true);
    specTitleField.set(null, specTitle);
}

如果vendorName不是NULL,其值并不重要。我的代码在eclipse中运行良好,但在导出的可运行jar包中出现异常。
当编写了上述代码并导出jar包后,最终成功了。

1

底部的链接有效。我删除了生成的两个非主类节,并将JAI规范放在顶部。在执行java -jar时,这样加载就可以了。 - Robert
对于其他人:这本质上是在这个答案中更好地描述的东西。 - eis
1
我将另一个答案设置为答案,因为 vendorName == null 的链接已解决该问题并重定向到网站的社区部分,而仅搜索该字符串只返回一个类似问题的提问。 - Robert

0
我遇到了同样的问题,并根据之前在这里得到的答案,为maven-shade-plugin构建了一个适合我的配置。我在这里将其作为其他人的模板包含进来:
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>2.2</version>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <manifestEntries>
              <Specification-Title>Java Advanced Imaging Image I/O Tools</Specification-Title>
              <Specification-Version>1.1</Specification-Version>
              <Specification-Vendor>Sun Microsystems, Inc.</Specification-Vendor>
              <Implementation-Title>com.sun.media.imageio</Implementation-Title>
              <Implementation-Version>1.1</Implementation-Version>
              <Implementation-Vendor>Sun Microsystems, Inc.</Implementation-Vendor>
            </manifestEntries>
          </transformer>
        </transformers>
      </configuration>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>  
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接