Извлечение классов, расширяющих "Randomizer" из всех jarfiles в classpath - PullRequest
3 голосов
/ 17 июля 2011

Субъект действительно говорит о моей цели, но я поставлен в тупик относительно реализации.У меня есть программа, которая принимает различные объекты, которые расширяют мой класс Randomizer.Я хочу сделать так, чтобы можно было поместить файл JAR в путь к классам, и программа запустит его при запуске и добавит в основную программу.Это то, что я пытался до сих пор, но я остановился, когда понял, что java.util.jar.JarFile не может дать вам Class es или Method s.

, так как он полагается на это, я мог бы какхорошо упомянуть, что мой класс 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, если это поможет.Я также пытаюсь сделать это без использования каких-либо библиотек.

Реализация решения


Я попытался реализовать решение, описанное Райаном Стюартом, и оно показано ниже.Я работаю с тестом 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 ]

4 голосов
/ 17 июля 2011

Типичным решением этой проблемы является создание файлов jar, в которых есть файл в META-INF, который сообщает вашей программе, какой класс (классы) загружать из этого jar. Пользовательские обработчики пространства имен Spring и Драйверы JDBC , например, загружаются таким образом.

Вместо того, чтобы выяснить, как на самом деле сканировать все классы в банке, что никто не делает, так что я ожидаю, что это невозможно / выполнимо, заставьте ваш код искать конкретный файл в каждом банке на пути к классам, который перечисляет Randomizer реализации он содержит. Например, ожидайте, что файл с именем META-INF / randomizers.list будет иметь список имен классов, по одному на строку, которые являются классами в этом фляге, которые реализуют ваш Randomizer. Прочитайте файл и для каждой строки используйте 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. Документы объясняют, как его использовать, и я также написал пример того, как его использовать. Вы можете найти код на 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 для загрузки трех служб, определенных в трех других модулях / банках, которые реализуют интерфейс, определенный в первом модуле / банке.

Редактировать: Поскольку у вас, похоже, возникают проблемы с примером проекта, вот основы.

  1. Каждый файл jar, содержащий одну или несколько реализаций Randomizer, должен содержать файл с именем 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();
    }
    
3 голосов
/ 05 августа 2011

Выбор списка классов из Манифеста, конечно, является предпочтительным решением.Но если вы не ожидаете, что банки будут содержать такую ​​информацию, вы можете прибегнуть к этому:

  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());
        }
      }
    }
  }
1 голос
/ 10 августа 2011

Я думаю, что вы должны придерживаться Java ServiceLoader. Тогда вам не нужно возиться с банками самостоятельно. Вот хороший урок: http://java.sun.com/developer/technicalArticles/javase/extensible/index.html

Если вы разрабатываете расширяемое настольное приложение, платформа NetBeans может быть правильным решением для вас. У него более высокая кривая обучения, но оно того стоит. http://netbeans.org/features/platform/index.html

1 голос
/ 10 августа 2011

Здесь приведен код, который я использую для извлечения всех классов, принадлежащих пакету и любым подпакетам. Вам просто нужно найти список возвращенных классов для тех, кто реализует 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;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...