从类路径中的所有jar文件中提取扩展“Randomizer”的类

时间:2011-07-16 23:40:35

标签: java jar loading

主题确实说明了我的目标,但我对实施感到困惑。我有一个程序,它接受扩展我的Randomizer类的不同对象。我想这样做,以便可以在类路径中放置一个JAR文件,程序将在运行它时搜索它,并将其添加到主程序中。到目前为止,这是我尝试过的,但当我意识到java.util.jar.JarFile无法给你Class es或Method时,我就停止了。

因为它依赖于它,我不妨提一下我的班级ArrayPP<T>就像ArrayList,但有很多方法。此处显示的addAll方法的作用类似于add方法,但具有多个参数或泛型T的对象数组。

  private static Randomizer[] loadExternalRandomizers() throws IOException
  {
    java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
    ArrayPP<Randomizer> r = new ArrayPP<>();
    if (classPath.isDirectory())
    {
      r.addAll(getRandomizersIn(classPath));
    }
    return r.toArray();
  }

  private static Randomizer[] getRandomizersIn(File dir) throws IOException
  {
    ArrayPP<Randomizer> r = new ArrayPP<>();
    java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {

      @Override
      public boolean accept(File pathname)
      {
        return pathname.isDirectory() || pathname.toString().endsWith(".jar");
      }
    });
    java.util.jar.JarFile jr;
    java.util.Enumeration<java.util.jar.JarEntry> entries;
    java.util.jar.JarEntry thisEntry;
    for (java.io.File f : fs)
    {
      if (f.isDirectory())
      {
        r.addAll(getRandomizersIn(f));
        continue;
      }
      jr = new java.util.jar.JarFile(f);
      entries = jr.entries();
      while (entries.hasMoreElements())
      {
        thisEntry = entries.nextElement();
        //if (the jar file contains a class that extends Randomizer
        //  add that class to r
      }
    }
    return r.toArray();
  }

我正在Java 7上构建它,如果这有帮助的话。我也希望不使用任何库来做到这一点。

实施解决方案


我尝试过实施Ryan Stewart描述的解决方案,如下所示。我正在使用名为JAR的测试BHR2 - Ranger.jar,其中包含一个在Randomizer包中扩展Ranger bhr2.plugins的类。 JAR在其META-INF\services文件夹中包含一个名为bhr2.plugins.Ranger的文件,其中一行显示为bhr2.Randomizer # Abstract Randomizer

  private static ArrayPP<Randomizer> loadExternalRandomizers() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
  {
    java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
    ArrayPP<Randomizer> r = new ArrayPP<>();
    if (classPath.isDirectory())
    {
      r.addAll(getRandomizersIn(classPath));
    }
    return r;
  }

  private static int depth = 0;
  private static ArrayPP<Randomizer> getRandomizersIn(File dir) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
  {
    ArrayPP<Randomizer> r = new ArrayPP<>();
    java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {

      @Override
      public boolean accept(File pathname)
      {
        return pathname.isDirectory() || pathname.toString().endsWith(".jar");
      }
    });
    for (java.io.File f : fs)
    {
      for (int i=0; i < depth; i++)
        System.out.print("  ");
      System.out.println(f);
      if (f.isDirectory())
      {
        if (depth < 4)
        {
          depth++;
          r.addAll(getRandomizersIn(f));
          depth--;
        }
        else
          System.out.println("Skipping directory due to depth");
        continue;
      }
        java.util.ServiceLoader<Randomizer> sl = ServiceLoader.loadInstalled(Randomizer.class);

        for (Randomizer rand : sl)
        {
          r.add(rand);
          System.out.println("adding " + rand);
        }
    }
    return r == null || r.isEmpty() ? new ArrayPP<Randomizer>() : r;
  }

但是当我运行它时,这是我在开始做其他事情之前得到的所有内容:

I:\Java\NetBeansProjects\BHRandomizer2\nbproject
  I:\Java\NetBeansProjects\BHRandomizer2\nbproject\private
I:\Java\NetBeansProjects\BHRandomizer2\src
  I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2
    I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\resources
    I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\randomizers
  I:\Java\NetBeansProjects\BHRandomizer2\src\bht
    I:\Java\NetBeansProjects\BHRandomizer2\src\bht\test
    I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools
      I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps
        I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps\gameboard
Skipping directory due to depth
      I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\effects
      I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\misc
      I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\utilities
    I:\Java\NetBeansProjects\BHRandomizer2\src\bht\resources
I:\Java\NetBeansProjects\BHRandomizer2\lib
  I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs
    I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs\org-netbeans-modules-java-j2seproject-copylibstask.jar
  I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout
    I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout\swing-layout-1.0.4.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger
  I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\META-INF
  I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2
    I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2\plugins
I:\Java\NetBeansProjects\BHRandomizer2\build
  I:\Java\NetBeansProjects\BHRandomizer2\build\classes
    I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2
      I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\randomizers
      I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\resources
    I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht
      I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools
        I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\comps
Skipping directory due to depth
        I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\utilities
Skipping directory due to depth
        I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\effects
Skipping directory due to depth
        I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\misc
Skipping directory due to depth
      I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\resources
      I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\test
    I:\Java\NetBeansProjects\BHRandomizer2\build\classes\META-INF
  I:\Java\NetBeansProjects\BHRandomizer2\build\empty
  I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources
    I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources\ap-source-output
I:\Java\NetBeansProjects\BHRandomizer2\dist
  I:\Java\NetBeansProjects\BHRandomizer2\dist\BHRandomizer2.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger.jar

4 个答案:

答案 0 :(得分:4)

此问题的典型解决方案是构建具有META-INF文件的jar文件,该文件告诉程序从该jar加载哪些类。例如,Spring custom namespace handlersJDBC drivers以这种方式加载。

而不是弄清楚如何实际扫描jar中的所有类,没有人这样做,所以我期望不可行/不可行,让你的代码在类路径的每个jar中查找特定文件,列出Randomizer它包含的实现。例如,期望一个名为META-INF / randomizers.list的文件有一个类名列表,每行一个,这是实现Randomizer的jar中的类。阅读文件,对于每一行,使用Class.forName()按名称加载类,然后newInstance()实例化它。

编辑:从类路径中的任何位置加载“列表”文件:

Enumeration<URL> resources = getClassLoader().getResources(
    "/META-INF/randomizers.list");
while (resources.hasMoreElements()) {
    URL url = resources.nextElement();
    // Load the Randomizer(s) specified in this file
}

编辑:所以事实证明JDK公开了它用于此类事情的机制,我以前不知道。只需使用ServiceLoader即可。 The docs解释了如何使用它,我也编写了一个如何使用它的例子。您可以find the code on github或者只是自己克隆并运行它:

git clone git://github.com/zzantozz/testbed.git tmp
cd tmp
mvn install -pl serviceloader-example/service-usage -am
mvn -q exec:java -D exec.mainClass=rds.serviceloader.ServiceLoaderExample -pl serviceloader-example/service-usage

该示例包含五个模块:一个定义服务接口,三个定义单独的服务实现,另一个使用ServiceLoader加载实现。最后一个称为“服务使用”,并演示如何使用ServiceLoader类加载在实现第一个模块/ jar中定义的接口的其他三个模块/ jar中定义的三个服务。

修改:由于您似乎遇到了示例项目的问题,以下是基础知识。

  1. 每个包含一个或多个Randomizer实现的jar文件应包含名为META-INF / services / com.foo.Randomizer的文件(其中com.foo是您的包名)。
  2. 此文件应包含一个类名列表,每行一个,每个都是Randomizer的一个实现。
  3. 有了这些文件,您需要做的就是获取所有Randomizer实例

    ServiceLoader<Randomizer> loader = ServiceLoader.load(Randomizer.class);
    for (Randomizer randomizer : loader) {
        randomizer.doWhatever();
    }
    

答案 1 :(得分:3)

从Manifest中获取类列表当然是更好的解决方案。但是如果你不希望罐子包含这些信息,你可以回到这个:

  private static void scanClasses(File file) throws MalformedURLException, IOException {
    ClassLoader classLoader = new URLClassLoader(new URL[]{ file.toURI().toURL() });
    JarFile jar = new JarFile(file.getAbsoluteFile());
    Enumeration<JarEntry> jarEntries = jar.entries();
    while (jarEntries.hasMoreElements()) {
      JarEntry je = jarEntries.nextElement();
      if (je.getName().endsWith(".class")) {
        String clazzName = je.getName();
        clazzName = clazzName.substring(0, clazzName.length() - ".class".length()).replaceAll("/", ".");
        clazzName = clazzName.replaceAll("/", ".");
        try {
          Class clazz = Class.forName(clazzName, false, classLoader);
          if (Randomizer.class.isAssignableFrom(clazz)) {
            System.out.println("Found Randomizer: " + clazz);
          }
        } catch (ClassNotFoundException ex) {
          // this really should not happen, 
          // since we *know* the class exists
          throw new AssertionError(ex.getMessage());
        }
      }
    }
  }

答案 2 :(得分:1)

这里是我用来检索属于包和任何子包的所有类的代码。您只需要使用反射搜索实现Randomizer的那些返回类的列表。

/**
 * Scans all classes accessible from the context class loader which belong
 * to the given package and subpackages.
 * <p>
 * Inspired from post on: {@code http://snippets.dzone.com/posts/show/4831}
 */
public static List<Class<?>> getClasses(String packageName)
        throws ClassNotFoundException, IOException {

    // Retrieving current class loader
    final ClassLoader classLoader
            = Thread.currentThread().getContextClassLoader();

    // Computing path from the package name
    final String path = packageName.replace('.', '/');

    // Retrieving all accessible resources
    final Enumeration<URL> resources = classLoader.getResources(path);
    final List<File> dirs = new ArrayList<File>();

    while (resources.hasMoreElements()) {
        final URL resource = resources.nextElement();
        final String fileName = resource.getFile();
        final String fileNameDecoded = URLDecoder.decode(fileName, "UTF-8");
        dirs.add(new File(fileNameDecoded));
    }

    // Preparing result
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    // Processing each resource recursively
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }

    // Returning result
    return classes;

}

/**
 * Recursive method used to find all classes in a given directory and
 * subdirs.
 */
public static List<Class<?>> findClasses(File directory, String packageName)
        throws ClassNotFoundException {

    // Preparing result
    final List<Class<?>> classes = new ArrayList<Class<?>>();

    if ( directory == null )
        return classes;

    if (!directory.exists())
        return classes;

    // Retrieving the files in the directory
    final File[] files = directory.listFiles();

    for (File file : files) {

        final String fileName = file.getName();

        // Do we need to go recursive?
        if (file.isDirectory()) {

            classes.addAll(findClasses(file, packageName + "." + fileName));

        } else if (fileName.endsWith(".class") && !fileName.contains("$")) {

            Class<?> _class;

                _class = Class.forName(packageName + '.'
                        + fileName.substring(0, fileName.length() - 6));

            classes.add(_class);

        }

    }

    return classes;

}

答案 3 :(得分:1)

我认为你应该坚持使用java ServiceLoader。那你就不需要自己摆弄罐子了。 这是一个很好的教程: http://java.sun.com/developer/technicalArticles/javase/extensible/index.html

如果您正在开发可扩展的桌面应用程序,netbeans平台可能是适合您的解决方案。它具有更高的学习曲线,但非常值得。 http://netbeans.org/features/platform/index.html