Як прочитати всі класи з пакету 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;
}

Дякуємо за фрагмент коду. :) Якщо ви хочете уникнути дублювання класу у списку кандидатів, змініть тип колекції зі Списку на Набір. І використовуйте hasEnclosingClass () та getEnclosingClassName () classMetaData. Використовуйте метод getEnclosingClass про базовий клас
трапер

29

Ви можете скористатися описаним тут проектом "Роздуми"

Він цілком повний і простий у використанні.

Короткий опис із зазначеного вище веб-сайту:

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

Приклад:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

Чи є робота в Equinox? І якщо так, то чи може це зробити без активації плагінів?
Тег

працює при рівнодення. у старих версіях 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

новий файл (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. Про сплячий режим уже написано:

    • 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: Ти нарешті відкрив його джерело? Якщо так: де ми можемо знайти останню версію? Дякую!
Джоаніс

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-адреси та каталоги на шляху до класу, немає простого способу отримати остаточний список класів.

Однак ви можете наблизитися досить близько. Деякі з весняних бібліотек роблять це зараз. Ви можете отримати всі банки на шляху до класу та відкрити їх як файли. Потім ви можете взяти цей список файлів і створити структуру даних, що містить ваші класи.


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.