Как я могу перечислить все классы в пакете и добавить их в список? - PullRequest
31 голосов
/ 07 октября 2008

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

List allClasses = new ArrayList();
allClasses.add(String.class);

Как я могу сделать это динамически, чтобы добавить все классы в пакет и все его подпакеты?


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

Обновление: Читая это снова, я вижу, как это неправильно читается. Я стремлюсь перечислить все классы МОЕГО ПРОЕКТА из файловой системы после компиляции.

Ответы [ 8 ]

37 голосов
/ 20 августа 2010

**** ОБНОВЛЕНИЕ 1 (2012) ****

ОК, наконец-то я приступил к очистке фрагмента кода ниже. Я вставил его в собственный проект github и даже добавил тесты.

https://github.com/ddopson/java-class-enumerator

**** ОБНОВЛЕНИЕ 2 (2016) ****

Для еще более надежного и многофункционального сканера пути к классам, см. https://github.com/lukehutch/fast-classpath-scanner/wiki. Я бы рекомендовал сначала прочитать мой фрагмент кода, чтобы получить общее представление, а затем использовать инструмент lukehutch для производственных целей.

**** Оригинальный пост (2010) ****

Строго говоря, невозможно перечислить классы в пакете . Это связано с тем, что пакет на самом деле является не чем иным, как пространством имен (например, com.epicapplications.foo.bar), и любой jar-файл в classpath потенциально может добавить классы в пакет. Хуже того, загрузчик классов будет загружать классы по требованию, и часть пути к классам может находиться на другой стороне сетевого подключения.

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

К сожалению, нет никакого фреймворка, который бы облегчил эту задачу. Вы должны сканировать файловую систему аналогично тому, как ClassLoader будет искать определения классов.

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

Чтобы все работало с файлами JAR, попробуйте это ...

private static ArrayList<Class<?>> getClassesForPackage(Package pkg) {
    String pkgname = pkg.getName();
    ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
    // Get a File object for the package
    File directory = null;
    String fullPath;
    String relPath = pkgname.replace('.', '/');
    System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath);
    URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
    System.out.println("ClassDiscovery: Resource = " + resource);
    if (resource == null) {
        throw new RuntimeException("No resource for " + relPath);
    }
    fullPath = resource.getFile();
    System.out.println("ClassDiscovery: FullPath = " + resource);

    try {
        directory = new File(resource.toURI());
    } catch (URISyntaxException e) {
        throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...", e);
    } catch (IllegalArgumentException e) {
        directory = null;
    }
    System.out.println("ClassDiscovery: Directory = " + directory);

    if (directory != null && directory.exists()) {
        // Get the list of the files contained in the package
        String[] files = directory.list();
        for (int i = 0; i < files.length; i++) {
            // we are only interested in .class files
            if (files[i].endsWith(".class")) {
                // removes the .class extension
                String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
                System.out.println("ClassDiscovery: className = " + className);
                try {
                    classes.add(Class.forName(className));
                } 
                catch (ClassNotFoundException e) {
                    throw new RuntimeException("ClassNotFoundException loading " + className);
                }
            }
        }
    }
    else {
        try {
            String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
            JarFile jarFile = new JarFile(jarPath);         
            Enumeration<JarEntry> entries = jarFile.entries();
            while(entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if(entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
                    System.out.println("ClassDiscovery: JarEntry: " + entryName);
                    String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
                    System.out.println("ClassDiscovery: className = " + className);
                    try {
                        classes.add(Class.forName(className));
                    } 
                    catch (ClassNotFoundException e) {
                        throw new RuntimeException("ClassNotFoundException loading " + className);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
        }
    }
    return classes;
}
3 голосов
/ 24 сентября 2015

Вы можете попробовать мою библиотеку FastClasspathScanner .

Чтобы перечислить все классы в пакете, выполните следующие действия:

Set<String> classNames = new FastClassPathScanner("com.mypackage")
    .scan()
    .getNamesOfAllClasses();
3 голосов
/ 10 октября 2008

Я понял, как это сделать. Вот процедура:

  1. Начните с класса в корневом пакете и получите папку, в которой он находится, из загрузчика классов
  2. Рекурсивно перечислять все файлы .class в этой папке
  3. Преобразование имен файлов в полные имена классов
  4. Используйте Class.forName (), чтобы получить классы

Здесь есть несколько неприятных уловок, которые меня немного смущают, но это работает - например:

  1. Преобразование имен путей в имена пакетов с использованием строковых манипуляций
  2. Жесткое кодирование имени корневого пакета, чтобы можно было убрать префикс пути

Очень жаль, что stackoverflow не позволяет мне принять мой собственный ответ ...

2 голосов
/ 11 августа 2011

Существует предостережение: контейнеры ApplicationEngines / servlet, такие как tomcat и JBoss, имеют иерархические загрузчики классов . Получить загрузчик системного класса не получится.

Способ Tomcat работает (вещи могли измениться, но мой текущий опыт не заставляет меня верить иначе), но у каждого контекста приложения есть свой загрузчик классов, так что классы для приложения «foo» не не сталкиваются с классами для приложения 'fooV2'

Просто в качестве примера. Если бы все классы были объединены в один контекст Uber-классов, вы бы не знали, используете ли вы классы, подходящие для версии 1 или версии 2.

Кроме того, каждому нужен доступ к системным классам, таким как java.lang.String. Это иерархия. Сначала он проверяет локальный контекст приложения и перемещается вверх (это моя текущая ситуация, кстати).

Чтобы справиться с этим, лучшим подходом будет: this.getClass (). GetClassloader ()

В моем случае у меня есть веб-служба, которая должна самостоятельно обнаруживать некоторые модули, и они, очевидно, находятся в «этом» контексте веб-службы или системном контексте. Делая выше, я получаю, чтобы проверить оба. Просто получая системный загрузчик классов, я не получаю доступ ни к одному из классов приложения (и, следовательно, мои ресурсы равны нулю).

2 голосов
/ 07 октября 2008

Забавно, что этот вопрос возникает время от времени. Проблема в том, что это ключевое слово было бы более правильно названо "пространство имен". Пакет Java не определяет конкретный контейнер, который одновременно содержит все классы в пакете. Он просто определяет токен, который классы могут использовать, чтобы объявить, что они являются членами этого пакета. Вам нужно будет выполнить поиск по всему пути к классам (как указано в другом ответе), чтобы определить все классы в пакете.

2 голосов
/ 07 октября 2008

Боюсь, вам придется вручную сканировать путь к классам и другие места, где java ищет классы (например, каталог ext или загрузочный путь к классам). Поскольку java использует ленивую загрузку классов, он может даже не знать о дополнительных классах в ваших пакетах, которые еще не были загружены. Также проверьте понятие «запечатанных» пакетов.

0 голосов
/ 05 октября 2017

Если вы просто хотите загрузить группу связанных классов, Spring может помочь вам.

Spring может создавать список или карту всех классов, которые реализуют данный интерфейс, в одной строке кода. Список или карта будут содержать экземпляры всех классов, которые реализуют этот интерфейс.

При этом в качестве альтернативы загрузке списка классов из файловой системы вместо этого просто реализуйте один и тот же интерфейс для всех классов, которые вы хотите загрузить, независимо от пакета. Таким образом, вы можете загрузить (и создать экземпляр) все нужные вам классы независимо от того, в каком пакете они находятся.

С другой стороны, если вы хотите иметь их все в пакете, просто включите все классы в этом пакете для реализации данного интерфейса.

0 голосов
/ 10 октября 2008

Посмотрите, что делает java.net.URLClassLoader. Он никогда не перечисляет классы, он просто пытается найти классы по запросу. Если вы хотите перечислить классы, вам нужно получить путь к классам, разбить его на каталоги и файлы jar. Сканирование каталогов (и их подкаталогов) и файлов JAR на наличие файлов с именем * .class.

Возможно, стоит взглянуть на проекты с открытым исходным кодом, которые, кажется, делают перечисление, которое вы хотите (например, Eclipse ) для вдохновения.

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