如何使用类加载器加载Apache XMLBeans类?

3
我们有一个报告应用程序,默认生成pdf输出,但您可以编写自己的类来生成任何其他输出格式。因此,我使用apache poi 10.0生成了xls文件。但是,现在出现了生成xlsx文件的请求。当我尝试使用以下代码创建工作簿时:
XSSFWorkbook wbTemplate=new XSSFWorkbook()

I got the error:

java.lang.NoSuchMethodError: org.apache.xmlbeans.XmlOptions.setSaveAggressiveNamespaces()Lorg/apache/xmlbeans/XmlOptions;

我发现应用程序使用的是一个非常老的xmlbeans文件版本,当然不包含上述方法。首先,我尝试用更新的版本替换xml bean文件,希望能有好运气,但应用程序却冻结了。
我的下一个想法是使用classLoader,当应用程序运行我的类以生成xlsx文件时,我加载上述方法。为此,我实现了在互联网上找到的这个解决方案:
URL[] classLoaderUrls = new URL[]{new URL("file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.6.0.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls);
Class<?> beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
Constructor<?> constructor = beanClass.getConstructor();
Object beanObj = constructor.newInstance();
Method[] m=beanClass.getMethods();
Method method = beanClass.getMethod("setSaveAggressiveNamespaces");
method.invoke(beanObj);

但是很惊讶的是,当它要获取“setSaveAggressiveNamespaces”方法名称时,我再次得到了该函数不存在的错误。然后,我将这个类的所有函数名写入文件中,确实发现这个名称不存在。但还有另一个名为“setSaveAggresiveNamespaces”的函数,只有一个S!如果我调用它,它就会起作用,但当我要创建XSSF工作簿时,仍然会出现“setSaveAggressiveNamespaces”(有两个SS)不存在的消息。但是,既然这是与apache poi软件包一起提供的,所以setSaveAggressiveNamespaces应该在类中。
在这种情况下我该怎么办才能使它工作? 应用程序运行在java 1.6下。
谢谢您的回答。
更新: Axel,这是我现在如何加载类的方式:
 public void customClassLoader() throws Exception
{
    URL[] classLoaderUrls = new URL[]{new URL("file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.3.0.jar")};
    URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls,null);
    Class<?> beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
    log("RESOURCES:" +beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class"));
    Constructor<?> constructor = beanClass.getConstructor();
    Object beanObj = constructor.newInstance();
    Method[] m=beanClass.getMethods();
    for (int i=0;i<m.length;++i)
        log("QQQ:" +String.valueOf(i)+".: "+ m[i].getName());
    Method method = beanClass.getMethod("setSaveAggressiveNamespaces");
    method.invoke(beanObj);
}

然后我在生成报告的类的第一行调用上述函数。在它之前没有任何内容。

资源写入日志中的内容为: "RESOURCES:jar:file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.3.0.jar!/org/apache/xmlbeans/XmlOptions.class"


那么现在更新的代码有什么问题呢?你所说的日志记录中写的似乎是正确的,不是吗?如果不是,为什么呢? - Axel Richter
1个回答

2

URLClassLoader(java.net.URL[]) 说明:

使用默认的委派父类加载器为指定的URL构造新的URLClassLoader。

因此,默认的委派父类加载器也将被使用,如果找到并且不在附加给定的URL中,则会从那里加载org.apache.xmlbeans.XmlOptions

所以,我们不需要使用默认的委派父类加载器。 URLClassLoader(java.net.URL[], null) 可以做到这一点。

示例:

import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Constructor;

public class UseURLClassLoader {

 public static void main(String[] args) throws Exception {
  URL[] classLoaderUrls;
  URLClassLoader urlClassLoader;
  Class<?> beanClass;

  classLoaderUrls = new URL[]{new URL("file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar")};
  urlClassLoader = new URLClassLoader(classLoaderUrls); //default delegation parent ClassLoader is used
  beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
System.out.println(beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class")); //class is loaded using default parent class loader

  URL context = new URL("file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/");
  classLoaderUrls = new URL[] {
   new URL(context, "poi-3.10.1-20140818.jar"),
   new URL(context, "poi-ooxml-3.10.1-20140818.jar"),
   new URL(context, "poi-ooxml-schemas-3.10.1-20140818.jar"),
   // maybe others also necessary
   new URL(context, "lib/commons-codec-1.5.jar"),
   // maybe others also necessary
   new URL(context, "ooxml-lib/xmlbeans-2.6.0.jar")
   // maybe others also necessary
  };
  for (int i = 0; i < classLoaderUrls.length; i++) {
System.out.println(classLoaderUrls[i]);
  }
  urlClassLoader = new URLClassLoader(classLoaderUrls, null); //set default parent class loader null
  beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
System.out.println(beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class")); //class is loaded using this class loader

 }

}

我被称为以下内容:
axel@arichter:~/Dokumente/JAVA/poi/poi-4.0.0$ java -cp .:./*:./lib/*:./ooxml-lib/* UseURLClassLoader 

它产生:

jar:file:/home/axel/Dokumente/JAVA/poi/poi-4.0.0/ooxml-lib/xmlbeans-3.0.1.jar!/org/apache/xmlbeans/XmlOptions.class
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-ooxml-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-ooxml-schemas-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/lib/commons-codec-1.5.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar
jar:file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar!/org/apache/xmlbeans/XmlOptions.class

首先,使用默认的父类加载器加载类。对于我来说,它从更新的 xmlbeans-3.0.1.jar 更远地加载 org.apache.xmlbeans.XmlOptions。对于你来说,它从旧的 xmlbeans-1.*.jar 更远地加载。这是因为这些 jar 在默认父类加载器的类路径中。
然后,第二部分代码将默认父类加载器设置为 null,因此只使用此类加载器加载类。
但是,混合使用类加载器很麻烦。正如我的代码所暗示的那样,将默认父类加载器设置为 null 后,我们需要将所有所需的类源都提供给当前类加载器。这通常变得非常昂贵。因此,不要将旧的 jar 放在类路径中总是比混合使用类加载器更好的解决方案。

亲爱的Axel, 感谢您花时间写下如此详细的答案,并提供了很好的示例。我会尽快测试并回复您。 - Szil
Alex,这很奇怪。基本上你的解决方案是可行的,但是我必须启动报表生成器两次。如果我第一次启动它,我会收到相同的消息,即 setSaveAggressiveNamespaces不存在。但第二次我收到了一个关于XSSFWorkbook的NOClassDefFound消息。在此阶段这是可以接受的,我想。你有任何想法为什么我必须运行报告两次才能生效类加载器吗? - Szil

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