Как мне прочитать все классы из пакета 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;
}

Спасибо за фрагмент кода. :) Если вы хотите избежать дублирования классов в списке кандидатов, измените тип коллекции с 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 этих классов. Если ваша программа загружается локально, проблем быть не должно.

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

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


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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.