2015-08-21 51 views
0

我正在写一个插件加载器 - 它加载不在类路径上的jar。我写了一个简单的自定义ClassLoader,它在构造函数中接受一个JarFile,并在JarFile中查找指定的类。这个加载器简单地覆盖了ClassLoader的findClass()方法,并且工作正常。自定义ClassLoader与资源加载

然后我确定我还需要能够从插件jar中获取资源。所以我重覆findResource()。这有意想不到的结果是导致基类插件类无法在jar中找到其他类:我得到了NoClassDefFoundErrors!

换句话说,如果我有plugin.jar包含为myplugin和MyPluginComponent:

  • ,如果我不重写findResource(),那么我可以加载的jar,创建为myplugin的实例,而这又可以创建一个MyPluginComponent。但是我找不到在jar中捆绑的资源。
  • 如果我做覆盖findResource(),那么我可以加载的jar,创建为myplugin,的实例,但如果为myplugin试图创建一个MyPluginComponent,我得到一个NoClassDefFoundError错误。

这表明findResource()的实现无法找到类文件 - 但它甚至没有被调用(根据我的日志记录),所以我不明白这是怎么回事。这种互动如何解决,我该如何解决?

我试着编写一个小型自包含示例,并且手动生成一个不会产生“不兼容幻数”错误的jar文件时遇到了困难。希望无论我做错了什么,都会从单独的类加载器中看出来。不便之处,感谢您的时间。

import com.google.common.io.CharStreams; 

import java.io.File; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.IOException; 

import java.lang.ClassLoader; 

import java.net.URL; 

import java.util.jar.JarEntry; 
import java.util.jar.JarFile; 

/** 
* Custom class loader for loading plugin classes. Adapted from 
* http://kalanir.blogspot.com/2010/01/how-to-write-custom-class-loader-to.html 
*/ 
public static class PluginLoader extends ClassLoader { 
    private JarFile jarFile_; 
    public PluginLoader(JarFile jarFile) { 
     super(Thread.currentThread().getContextClassLoader()); 
     jarFile_ = jarFile; 
    } 

    @Override 
    public Class findClass(String className) { 
     try { 
      // Replace "." with "/" for seeking through the jar. 
      String classPath = className.replace(".", "/") + ".class"; 
      System.out.println("Searching for " + className + " under " + classPath); 
      JarEntry entry = jarFile_.getJarEntry(classPath); 
      if (entry == null) { 
       return null; 
      } 
      InputStream stream = jarFile_.getInputStream(entry); 
      String contents = CharStreams.toString(
        new InputStreamReader(stream)); 
      stream.close(); 
      byte[] bytes = contents.getBytes(); 
      Class result = defineClass(className, bytes, 0, bytes.length); 
      return result; 
     } 
     catch (IOException e) { 
      System.out.println(e + "Unable to load jar file " + jarFile_.getName()); 
     } 
     catch (ClassFormatError e) { 
      System.out.println(e + "Unable to read class data for class " + className + " from jar " + jarFile_.getName()); 
     } 
     return null; 
    } 

    @Override 
    protected URL findResource(String name) { 
     System.out.println("Asked to find resource at " + name); 
     try { 
      String base = new File(jarFile_.getName()).toURI().toURL().toString(); 
      URL result = new URL(String.format("jar:%s!/%s", base, name)); 
      System.out.println("Result is " + result); 
      return result; 
     } 
     catch (IOException e) { 
      System.out.println(e + "Unable to construct URL to find " + name); 
      return null; 
     } 
    } 

    @Override 
    public InputStream getResourceAsStream(String name) { 
     System.out.println("Getting resource at " +name); 
     JarEntry entry = jarFile_.getJarEntry(name); 
     if (entry == null) { 
      System.out.println("Couldn't find resource " + name); 
      return null; 
     } 
     try { 
      return jarFile_.getInputStream(entry); 
     } 
     catch (IOException e) { 
      System.out.println(e + "Unable to load resource " + name + " from jar file " + jarFile_.getName()); 
      return null; 
     } 
    } 
} 
+0

为什么不使用'URLClassLoader'有什么具体原因吗?它给你所有这些功能。 – KDM

+0

@KDM,因为我不知道该怎么办。这很奇怪,因为我在这方面做了很多搜索。由于最近从度假返回,我会把它归咎于愚蠢。 – chris

回答

0

如果你的要求是刚读额外的JAR文件扩展URLClassLoader可能是一个更好的选择。

public class PluginLoader extends URLClassLoader { 

    public PluginLoader(String jar) throws MalformedURLException { 
     super(new URL[] { new File(jar).toURI().toURL() }); 
    } 

} 
+0

奇怪的是我没有偶然发现这个选项。它产生的结果与我手动加载资源的ClassLoader类似:我可以看到基类插件类,但是当该类试图引用jar中的另一个类时,我得到一个NoClassDefFoundError。奇怪的是我可以通过日志记录看到次要类加载得很好,但我仍然得到错误。 – chris

+0

好吧,我想我想通了:我有一个静态成员字段,其初始化涉及无效的投射操作。因此该类不能被实例化。为什么这会显示为NoClassDefFoundError,我不知道,但我认为我现在可以继续。感谢您的帮助。 – chris