如何使用classloader加载apache xmlbeans类?

时间:2018-10-14 04:35:57

标签: java apache-poi classloader

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

XSSFWorkbook wbTemplate=new XSSFWorkbook()

我得到了错误:

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”方法名称时,我感到很惊讶,我再次得到该函数不存在的错误。 然后,我将此类的所有函数名称写入文件,这是事实,该名称不存在。但是,存在另一个带有一个S的“ setSaveAggresiveNamespaces”!如果我调用它可以正常工作,但是当我不想创建XSSF工作簿时,我仍然会收到一条消息,提示setSaveAggressiveNamespaces(带有双SS)不存在。 但是setSaveAggressiveNamespaces应该在该类中,因为它与apache poi软件包一起提供。

在这种情况下,我该怎么办? 该应用程序在Java 1.6下运行

预先感谢您的回答。

UPDATE

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);
}

然后在生成报告的类的第一行调用上述函数。没什么。

RESOURCE在日志中写为: “ RESOURCES:jar:file:/ C:/ HOME / Installs / Apache POI / poi-3.10 / ooxml-lib / xmlbeans-2.3.0.jar!/org/apache/xmlbeans/XmlOptions.class”

1 个答案:

答案 0 :(得分:2)

URLClassLoader(java.net.URL[])状态:

  

使用

为指定的URL 构造新的URLClassLoader。   默认的委托父类ClassLoader

因此,还将使用默认的委托父类ClassLoader,因此将从那里加载org.apache.xmlbeans.XmlOptions,而不从其他给定的URL加载。

因此,我们不需要不需要使用默认的委托父类ClassLoader。 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

因此,首先使用默认的父类加载器加载该类。对我来说,它加载的org.apache.xmlbeans.XmlOptions距离较新的xmlbeans-3.0.1.jar更远。对您来说,它的加载距离较早的xmlbeans-1.*.jar更远。这是因为这些jar位于默认父类加载器的类路径中。

然后,第二个代码部分将默认的父类加载器设置为null,因此仅使用该类加载器加载类。

但是弄乱类加载器是一团糟。正如我的代码所暗示的,将默认的父类加载器设置为null,我们需要为当前的类加载器提供所有所需的类源。这通常变得非常昂贵。因此,在类路径中使用旧的jar总是比弄乱类加载器更好的解决方案。