Можете ли вы найти все классы в пакете, используя отражение? - PullRequest
481 голосов
/ 06 февраля 2009

Можно ли найти все классы или интерфейсы в данном пакете? (При быстром взгляде, например, Package, кажется, что нет.)

Ответы [ 24 ]

343 голосов
/ 06 февраля 2009

Из-за динамической природы загрузчиков классов это невозможно. Загрузчики классов не обязаны сообщать виртуальной машине, какие классы она может предоставить, вместо этого они просто передают запросы на классы и должны возвращать класс или выдавать исключение.

Однако, если вы напишите свои собственные загрузчики классов или изучите пути к классам и их jar-файлы, можно найти эту информацию. Это будет через операции файловой системы, а не отражение. Могут даже быть библиотеки, которые могут помочь вам сделать это.

Если есть классы, которые генерируются или доставляются удаленно, вы не сможете обнаружить эти классы.

Обычный метод - вместо этого где-то зарегистрировать классы, к которым вам нужен доступ, в файле или ссылаться на них в другом классе. Или просто используйте соглашение, когда дело доходит до именования.

Приложение: Библиотека отражений позволит вам искать классы в текущем пути к классам. Может использоваться для получения всех классов в пакете:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);
175 голосов
/ 05 марта 2012

Возможно, вам стоит взглянуть на открытый исходный код Библиотека отражений . С его помощью вы можете легко достичь того, что вы хотите.

Сначала настройте индекс отражений (он немного запутанный, поскольку поиск всех классов по умолчанию отключен):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

Затем вы можете запросить все объекты в данном пакете:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
111 голосов
/ 20 ноября 2012

Google Guava 14 включает новый класс ClassPath с тремя методами сканирования классов верхнего уровня:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

См. ClassPath javadocs для получения дополнительной информации.

99 голосов
/ 06 февраля 2009

Вы можете использовать этот метод 1 , который использует ClassLoader.

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1 Этот метод был взят из http://snippets.dzone.com/posts/show/4831,, который был заархивирован Интернет-архивом, как это связано с настоящим моментом. Фрагмент также доступен на https://dzone.com/articles/get-all-classes-within-package.

82 голосов
/ 29 января 2014

Весна

Этот пример для Spring 4, но вы можете найти сканер classpath и в более ранних версиях.

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google Guava

Примечание: В версии 14 API по-прежнему помечен как @ Beta , так что будьте осторожны в рабочем коде.

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}
36 голосов
/ 17 марта 2014

Привет. У меня всегда были некоторые проблемы с решениями выше (и на других сайтах).
Я, как разработчик, программирую аддон для API. API предотвращает использование любых внешних библиотек или сторонних инструментов. Установка также состоит из смеси кода в файлах jar или zip и файлов классов, расположенных непосредственно в некоторых каталогах. Таким образом, мой код должен был работать в любой ситуации. После долгих исследований я нашел метод, который будет работать как минимум в 95% всех возможных установок.

Следующий код - это метод overkill, который всегда будет работать.

Код:

Этот код сканирует данный пакет для всех включенных в него классов. Это будет работать только для всех классов в текущем ClassLoader.

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

Эти три метода предоставляют вам возможность найти все классы в данном пакете.
Вы используете это так:

getClassesForPackage("package.your.classes.are.in");

Объяснение:

Метод сначала получает текущий ClassLoader. Затем он выбирает все ресурсы, которые содержат указанный пакет, и выполняет итерации этих URL s. Затем он создает URLConnection и определяет, какой тип URl у нас есть. Это может быть каталог (FileURLConnection) или каталог внутри файла jar или zip (JarURLConnection). В зависимости от типа соединения у нас будут вызываться два разных метода.

Сначала давайте посмотрим, что произойдет, если это FileURLConnection.
Сначала проверяется, существует ли переданный файл и является ли он каталогом. Если это так, он проверяет, является ли это файлом класса. Если так, то объект Class будет создан и помещен в ArrayList. Если это не файл класса, а каталог, мы просто перебираем его и делаем то же самое. Все остальные дела / файлы будут игнорироваться.

Если URLConnection является JarURLConnection, будет вызван другой частный вспомогательный метод. Этот метод перебирает все записи в архиве zip / jar. Если одна запись является файлом класса и находится внутри пакета, объект Class будет создан и сохранен в ArrayList.

После того, как все ресурсы проанализированы, он (основной метод) возвращает ArrayList, содержащую все классы в данном пакете, о которых знает текущий ClassLoader.

Если процесс завершится неудачей в любой момент, будет выдан ClassNotFoundException с подробной информацией о точной причине.

14 голосов
/ 18 февраля 2014

Без использования дополнительных библиотек:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}
13 голосов
/ 22 декабря 2012

Обычно загрузчики классов не позволяют сканировать все классы на пути к классам. Но обычно единственным используемым загрузчиком классов является UrlClassLoader, из которого мы можем получить список каталогов и файлов JAR (см. getURLs ) и открыть их один за другим, чтобы вывести список доступных классов. Этот подход, называемый сканированием пути класса, реализован в Scannotation и Reflections .

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

Другой подход заключается в использовании API обработки подключаемых аннотаций Java для написания процессора аннотаций, который будет собирать все аннотированные классы во время компиляции и создания файла индекса для использования во время выполнения. Этот механизм реализован в библиотеке ClassIndex :

// package-info.java
@IndexSubclasses
package my.package;

// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

Обратите внимание, что никаких дополнительных настроек не требуется, поскольку сканирование полностью автоматизировано, благодаря компилятору Java, автоматически обнаруживающему любые процессоры, найденные в пути к классам.

6 голосов
/ 31 июля 2016

Я написал FastClasspathScanner , чтобы решить эту проблему. Он обрабатывает множество различных типов задач сканирования пути к классам, имеет простой API, работает со многими различными ClassLoaders и средами путей к классам, был тщательно распараллелен и оптимизирован для высокой скорости и низкого потребления памяти. Он может даже генерировать визуализацию GraphViz графа классов, показывающую, как классы связаны друг с другом.

Для вашего первоначального вопроса о поиске всех классов или интерфейсов в данном пакете вы можете сделать:

List<String> classNames = new FastClasspathScanner("com.mypackage").scan()
    .getNamesOfAllClasses();

Существует множество возможных вариантов - для получения полной информации см. Документацию (ссылка выше).

5 голосов
/ 17 августа 2016

Вот как я это делаю. Я сканирую все подпапки (подпакеты) и не пытаюсь загрузить анонимные классы:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  
...