Как мне прочитать все классы из пакета Java в пути к классам?

94

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

List<Class> classes = readClassesFrom("my.package")
Йорген Р
источник
7
Проще говоря, нет, вы не можете сделать это легко. Есть несколько очень длинных приемов, которые работают в некоторых ситуациях, но я настоятельно рекомендую другой дизайн.
skaffman
Решение можно найти в проекте Weld .
Ondra ižka
Обратитесь к этой ссылке для ответа: stackoverflow.com/questions/176527/…
Karthik E

Ответы:

48

Если у вас есть Spring в пути к классам, то это сделает следующее.

Найдите все классы в пакете, помеченные XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}
Шалом938
источник
Спасибо за фрагмент кода. :) Если вы хотите избежать дублирования классов в списке кандидатов, измените тип коллекции с List на Set. И используйте hasEnclosingClass () и getEnclosingClassName () из classMetaData. Используйте метод getEnclosingClass для базового класса
traeper
29

Вы можете использовать проект Reflections, описанный здесь

Он довольно полный и простой в использовании.

Краткое описание с указанного выше сайта:

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

Пример:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Ренато
источник
Работает ли в Equinox? И если да, то можно ли это сделать без активации плагинов?
Tag
работает для равноденствия. в более старых версиях Reflections добавьте пакет jar vfsType. см. здесь
zapp
28

Я использую этот, он работает с файлами или jar-архивами

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}
Эдоардо Панфили
источник
1
это работает , если вы берете ссылку на File.separator и просто использовать «/»
Will Glass
1
Еще одно изменение требуется для работы с файлами jar, если путь содержит пробелы. Вы должны расшифровать путь к файлу jar. Изменение: jarFileName = packageURL.getFile (); кому: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass
я получаю только имя пакета в jar..не получаю имя класса
AutoMEta
new File (uri) исправит вашу проблему с пробелами.
Trejkaz
entryName.lastIndexOf ('.') будет -1
marstone
11

Spring реализовал отличную функцию поиска пути к классам в PathMatchingResourcePatternResolver. Если вы используете classpath*префикс:, вы можете найти все ресурсы, включая классы в данной иерархии, и даже отфильтровать их, если хотите. Затем вы можете использовать ребенок AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterи AssignableTypeFilterфильтровать эти ресурсы либо на аннотациях на уровне класса или интерфейс они реализуют.

Джон Эллинвуд
источник
6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

Это решение было протестировано в среде EJB .

Пол Куит
источник
6

Scannotation и Reflections используют подход сканирования пути к классам:

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

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

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Славек
источник
3

Насколько мне известно, эта функциональность все еще подозрительно отсутствует в API отражения Java. Вы можете получить объект пакета, просто сделав следующее:

Package packageObj = Package.getPackage("my.package");

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

Я нашел несколько примеров реализации в этом посте

Я не уверен на 100%, что эти методы будут работать, когда ваши классы похоронены в файлах JAR, но я надеюсь, что один из них сделает это за вас.

Я согласен с @skaffman ... если у вас есть другой способ сделать это, я бы рекомендовал сделать это вместо этого.

Брент пишет код
источник
4
Это не подозрительно, просто так не работает. Классы не «принадлежат» пакетам, они имеют на них ссылки. Ассоциация не указывает в обратном направлении.
skaffman
1
@skaffman Очень интересный момент. Никогда не думал об этом так. Итак, пока мы идем по этому следу мысли, почему ассоциация не является двунаправленной (сейчас это больше для моего собственного любопытства)?
Брент пишет код
3

Наиболее надежным механизмом для перечисления всех классов в данном пакете в настоящее время является ClassGraph , поскольку он обрабатывает максимально широкий спектр механизмов спецификации пути к классам , включая новую модульную систему JPMS. (Я автор.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Люк Хатчисон
источник
Я наткнулся на это пару лет спустя. Это отличный инструмент! Он работает намного быстрее, чем отражение, и мне нравится, что он не вызывает статические инициализаторы. Как раз то, что мне нужно для решения нашей проблемы.
akagixxer
2

eXtcos выглядит многообещающе. Представьте, что вы хотите найти все классы, которые:

  1. Расширить класс "Компонент" и сохранить их
  2. Аннотированы "MyComponent", и
  3. Находятся в «общей» упаковке.

С eXtcos это так же просто, как

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});
Ноелоб
источник
2
  1. Билл Берк написал (красивую статью о сканировании классов), а затем написал Scannotation .

  2. В Hibernate уже написано:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI может решить эту проблему, но не знаю - еще не исследовал полностью

.

@Inject Instance< MyClass> x;
...
x.iterator() 

Также для аннотаций:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ондра Жижка
источник
Ссылка на статью мертва.
user2418306
1

Я реализовал его, и в большинстве случаев он работает. Так как он длинный, я положил его сюда в файл .

Идея состоит в том, чтобы найти местоположение исходного файла класса, который доступен в большинстве случаев (известным исключением являются файлы классов JVM - насколько я тестировал). Если код находится в каталоге, просканируйте все файлы и найдите только файлы классов. Если код находится в файле JAR, просканируйте все записи.

Этот метод можно использовать только когда:

  1. У вас есть класс, который находится в том же пакете, который вы хотите обнаружить. Этот класс называется SeedClass. Например, если вы хотите перечислить все классы в 'java.io', начальный класс может быть java.io.File.

  2. Ваши классы находятся в каталоге или в файле JAR, в котором есть информация об исходном файле (не файл исходного кода, а только исходный файл). Насколько я пробовал, он работает почти на 100%, за исключением класса JVM (эти классы поставляются с JVM).

  3. Ваша программа должна иметь разрешение на доступ к ProtectionDomain этих классов. Если ваша программа загружается локально, проблем быть не должно.

Я тестировал программу только для регулярного использования, поэтому проблема все еще может быть.

Надеюсь, это поможет.

NawaMan
источник
1
это оказалось очень интересным материалом! я попробую его использовать. Если я считаю полезным для себя, могу ли я использовать ваш код в проекте с открытым исходным кодом?
1
Я тоже готовлюсь к открытию исходного кода. Так что вперед: D
NawaMan
@NawaMan: Вы наконец открыли исходный код? Если да, то где найти последнюю версию? Спасибо!
Joanis
1

Вот еще один вариант, небольшая модификация другого ответа выше / ниже:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);
Алексей Рашков
источник
0

Когда-то апплеты были обычным явлением, в пути к классам мог быть URL-адрес. Когда загрузчику классов требовался класс, он выполнял поиск во всех местах пути к классам, включая ресурсы http. Поскольку в пути к классам могут быть такие вещи, как URL-адреса и каталоги, нет простого способа получить окончательный список классов.

Однако можно подойти довольно близко. Некоторые библиотеки Spring делают это сейчас. Вы можете получить все файлы jar в пути к классам и открывать их как файлы. Затем вы можете взять этот список файлов и создать структуру данных, содержащую ваши классы.

Brianegge
источник
0

использовать maven зависимости:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

затем используйте этот код:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });
Ялами
источник
-2

Brent - причина, по которой ассоциация является одним из способов, связана с тем фактом, что любой класс любого компонента вашего CLASSPATH может объявить себя в любом пакете (кроме java / javax). Таким образом, просто нет отображения ВСЕХ классов в данном «пакете», потому что никто не знает и не может знать. Вы можете обновить файл jar завтра и удалить или добавить классы. Это все равно, что пытаться получить список всех людей по имени Джон / Джон / Йохан во всех странах мира - никто из нас не всеведущ, поэтому ни у кого из нас никогда не будет правильного ответа.

Идарвин
источник
Хороший философский ответ, но как тогда работает сканирование компонентов CDI? Или как Hibernate сканирует @Entities?
Ondra ižka