Как я могу перечислить все классы в пакете и добавить их в список?
Мне нужно перечислить все классы в пакете и добавить их в список. Нединамическая версия для одного класса выглядит следующим образом:
List allClasses = new ArrayList();
allClasses.add(String.class);
Как я могу сделать это динамически, чтобы добавить все классы в пакет и все его подпакеты?
Обновление: после прочтения ранних ответов совершенно верно, что я пытаюсь решить еще одну второстепенную проблему, поэтому позвольте мне заявить об этом. И я знаю, что это возможно, так как другие инструменты делают это. Смотрите новый вопрос здесь.
Обновление: читая это снова, я вижу, как это неправильно читается. Я хочу перечислить все классы МОЕ ПРОЕКТ из файловой системы после компиляции.
8 ответов
**** ОБНОВЛЕНИЕ 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-файл в пути к классам может потенциально добавить классы в пакет. Хуже того, загрузчик классов будет загружать классы по требованию, и часть пути к классам может находиться на другой стороне сетевого подключения.
Возможно решить более ограничительную проблему. например, все классы в файле 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;
}
Вы можете попробовать мою библиотеку FastClasspathScanner.
Чтобы перечислить все классы в пакете, сделайте следующее:
Set<String> classNames = new FastClassPathScanner("com.mypackage")
.scan()
.getNamesOfAllClasses();
Я понял, как это сделать. Вот процедура:
- Начните с класса в корневом пакете и получите папку, в которой он находится, из загрузчика классов
- Рекурсивно перечислять все файлы.class в этой папке
- Преобразовать имена файлов в полные имена классов
- Используйте Class.forName(), чтобы получить классы
Здесь есть несколько неприятных уловок, которые делают меня немного неловким, но это работает - например:
- Преобразование имен путей в имена пакетов с использованием строковых манипуляций
- Жесткое кодирование имени корневого пакета, чтобы можно было убрать префикс пути
Жаль, что stackru не позволяет мне принять мой собственный ответ...
Забавно, что этот вопрос возникает время от времени. Проблема в том, что это ключевое слово было бы более правильно названо "пространство имен". Пакет Java не определяет конкретный контейнер, который одновременно содержит все классы в пакете. Он просто определяет токен, который классы могут использовать, чтобы объявить, что они являются членами этого пакета. Вам нужно будет выполнить поиск по всему пути к классам (как указано в другом ответе), чтобы определить все классы в пакете.
Здесь есть одна оговорка: контейнеры ApplicationEngines/servlet, такие как tomcat и JBoss, имеют иерархические загрузчики классов. Получить загрузчик системного класса не получится.
То, как работает Tomcat (возможно, все изменилось, но мой текущий опыт не заставляет меня думать иначе), но каждый контекст приложения имеет свой собственный загрузчик классов, так что классы для приложения 'foo' не конфликтуют с классами для приложения 'fooV2'
Просто в качестве примера. Если бы все классы были объединены в один контекст Uber-класса, вы бы не знали, используете ли вы классы, подходящие для версии 1 или версии 2.
Кроме того, каждому нужен доступ к системным классам, таким как java.lang.String. Это иерархия. Сначала он проверяет локальный контекст приложения и перемещается вверх (это моя текущая ситуация, кстати).
Чтобы справиться с этим, лучшим подходом было бы это: this.getClass (). GetClassloader()
В моем случае у меня есть веб-сервис, который должен самостоятельно обнаруживать некоторые модули, и они, очевидно, находятся в "этом" контексте веб-сервиса или в системном контексте. Делая выше, я получаю, чтобы проверить оба. Просто получая системный загрузчик классов, я не получаю доступ ни к одному из классов приложения (и, следовательно, мои ресурсы равны нулю).
Боюсь, вам придется вручную сканировать путь к классам и другие места, где java ищет классы (например, каталог ext или загрузочный путь к классам). Поскольку java использует ленивую загрузку классов, он может даже не знать о дополнительных классах в ваших пакетах, которые еще не были загружены. Также проверьте понятие "запечатанные" пакеты.
Если вы просто хотите загрузить группу связанных классов, Spring поможет вам.
Spring может создавать список или карту всех классов, которые реализуют данный интерфейс, в одной строке кода. Список или карта будут содержать экземпляры всех классов, которые реализуют этот интерфейс.
При этом в качестве альтернативы загрузке списка классов из файловой системы вместо этого просто реализуйте один и тот же интерфейс во всех классах, которые вы хотите загрузить, независимо от пакета. Таким образом, вы можете загрузить (и создать экземпляр) все нужные вам классы независимо от того, в каком пакете они находятся.
С другой стороны, если вы хотите иметь их все в пакете, просто включите все классы в этом пакете для реализации данного интерфейса.
Посмотрите, что делает java.net.URLClassLoader. Он никогда не перечисляет классы, он просто пытается найти классы по запросу. Если вы хотите перечислить классы, вам нужно получить путь к классам, разбить его на каталоги и файлы jar. Сканирование каталогов (и их подкаталогов) и файлов JAR на наличие файлов с именем *.class.
Возможно, стоит взглянуть на проекты с открытым исходным кодом, которые, кажется, делают перечисление, которое вы хотите (например, Eclipse) для вдохновения.