Найти классы Java, реализующие интерфейс

Некоторое время назад я натолкнулся на фрагмент кода, который использовал некоторые стандартные функции Java для определения классов, которые реализовали данный интерфейс. Я знаю, что функции были скрыты в каком-то нелогичном месте, но их можно было использовать для других классов, как и предполагалось в имени пакета. Тогда мне это не нужно, поэтому я забыл об этом, но теперь я делаю, и я не могу найти функции снова. Где можно найти эти функции?

Изменить: я не ищу каких-либо функций IDE или что-то, а скорее что-то, что может быть выполнено в приложении Java.

9 ответов

Решение

Недавно я собрал пакет для того, чтобы делать то, что вы хотите, и многое другое. (Мне это нужно было для утилиты, которую я писал). Он использует библиотеку ASM. Вы можете использовать отражение, но ASM оказался лучше.

Я поместил свой пакет в библиотеку с открытым исходным кодом, которая есть на моем веб-сайте. Библиотека находится здесь: http://software.clapper.org/javautil/. Вы хотите начать с класса ClassFinder.

Утилита, для которой я ее написал, - это программа для чтения RSS, которую я все еще использую каждый день, поэтому код, как правило, выполняется. Я использую ClassFinder для поддержки плагина API в программе чтения RSS; при запуске он просматривает пару деревьев каталогов для jar-файлов и файлов классов, содержащих классы, которые реализуют определенный интерфейс. Это намного быстрее, чем вы могли ожидать.

Библиотека имеет BSD-лицензию, поэтому вы можете безопасно связать ее со своим кодом. Источник доступен.

Если это полезно для вас, помогите себе.

Обновление: если вы используете Scala, вы можете найти эту библиотеку более удобной для Scala.

Весна может сделать это для вас...

BeanDefinitionRegistry bdr = new SimpleBeanDefinitionRegistry();
ClassPathBeanDefinitionScanner s = new ClassPathBeanDefinitionScanner(bdr);

TypeFilter tf = new AssignableTypeFilter(CLASS_YOU_WANT.class);
s.addIncludeFilter(tf);
s.scan("package.you.want1", "package.you.want2");       
String[] beans = bdr.getBeanDefinitionNames();

NB. TypeFilter важен, если вы хотите получить правильные результаты! Вы также можете использовать здесь фильтры исключения.

Сканер может быть найден в банке Spring-Context, в реестре Spring-Bean, фильтр типов находится в Spring-Core.

Мне очень нравится библиотека отражений для этого.

Он предоставляет множество различных типов сканеров (getTypesAnnotatedWith, getSubTypesOfи т. д.), и это очень просто написать или расширить свой собственный.

Код, о котором вы говорите, звучит как ServiceLoader , который был введен в Java 6 для поддержки функции, которая была определена начиная с Java 1.3 или более ранней. По соображениям производительности это рекомендуемый подход для поиска реализаций интерфейса во время выполнения; если вам нужна поддержка в более старой версии Java, я надеюсь, что вы найдете мою реализацию полезной.

Есть несколько реализаций этого в более ранних версиях Java, но в пакетах Sun, а не в основном API (я думаю, что есть некоторые классы внутри ImageIO, которые делают это). Поскольку код прост, я бы порекомендовал предоставить собственную реализацию, а не полагаться на нестандартный код Sun, который может быть изменен.

Аннотации уровня пакета

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

Хотя найти все классы в JVM довольно сложно, на самом деле довольно легко просматривать иерархию пакетов.

Package[] ps = Package.getPackages();
for (Package p : ps) {
  MyAno a = p.getAnnotation(MyAno.class)
  // Recursively descend
}

Затем просто сделайте, чтобы у вашей аннотации был аргумент массива Class. Затем в вашем package-info.java для определенного пакета поместите MyAno.

Я добавлю больше деталей (код), если людям это интересно, но, скорее всего, идея получится.

MetaInf Service Loader

Чтобы добавить ответ @erickson, вы также можете использовать подход загрузчика сервисов. У Kohsuke есть замечательный способ генерировать необходимый материал META-INF, необходимый для подхода с загрузчиком сервисов:

http://weblogs.java.net/blog/kohsuke/archive/2009/03/my_project_of_t.html

Вы также можете использовать Extensible Component Scanner (extcos: http://sf.net/projects/extcos) и искать все классы, реализующие интерфейс, например, так:

Set<Class<? extends MyInterface>> classes = new HashSet<Class<? extends MyInterface>>();

ComponentScanner scanner = new ComponentScanner();
scanner.getClasses(new ComponentQuery() {
    @Override
    protected void query() {
        select().
        from("my.package1", "my.package2").
        andStore(thoseImplementing(MyInterface.class).into(classes)).
        returning(none());
    }
});

Это работает для классов в файловой системе, внутри jar-файлов и даже для классов в виртуальной файловой системе JBoss. Кроме того, он предназначен для работы в автономных приложениях, а также в любом веб-контейнере или контейнере приложений.

В полной общности эта функциональность невозможна. Механизм Java ClassLoader гарантирует только возможность запрашивать класс с определенным именем (включая pacakge), а ClassLoader может предоставить класс или он может указать, что он не знает этот класс.

Классы могут быть (и часто) загружаются с удаленных серверов, и они могут даже создаваться на лету; совсем нетрудно написать ClassLoader, который возвращает допустимый класс, который реализует данный интерфейс для любого имени, которое вы у него запрашиваете; Список классов, реализующих этот интерфейс, будет бесконечным по длине.

На практике наиболее распространенным случаем является URLClassLoader который ищет классы в списке каталогов файловой системы и JAR-файлов. Так что вам нужно, чтобы получить URLClassLoaderзатем выполните итерацию по этим каталогам и архивам, и для каждого найденного в них файла класса запросите соответствующий Class возразить и посмотреть на возвращение его getInterfaces() метод.

Очевидно, Class.isAssignableFrom() сообщает вам, реализует ли отдельный класс данный интерфейс. Итак, проблема в том, чтобы получить список классов для тестирования.

Насколько я знаю, у Java нет прямого способа запросить у загрузчика классов "список классов, которые вы потенциально можете загрузить". Поэтому вам придется делать это самостоятельно, перебирая видимые файлы jar, вызывая Class.forName() для загрузки класса, а затем тестируя его.

Однако немного проще, если вы просто хотите узнать классы, реализующие данный интерфейс, из тех, которые были фактически загружены:

  • через среду инструментария Java вы можете вызвать Instrumentation.getAllLoadedClasses()
  • через отражение вы можете запросить поле ClassLoader.classes заданного ClassLoader.

Если вы используете технику инструментирования, то (как объяснено в ссылке) происходит то, что ваш класс "агента" вызывается по существу, когда JVM запускается и передает объект Instrumentation. В этот момент вы, вероятно, захотите "сохранить его на потом" в статическом поле, а затем сделать так, чтобы ваш основной код приложения вызывал его позже, чтобы получить список загруженных классов.

Если вы спрашивали с точки зрения работы с запущенной программой, вам нужно обратиться к пакету java.lang. *. Если вы получаете объект Class, вы можете использовать метод isAssignableFrom, чтобы проверить, является ли он интерфейсом другого Class.

Нет простого встроенного способа их поиска, такие инструменты, как Eclipse, создают индекс этой информации.

Если у вас нет определенного списка объектов Class для тестирования, вы можете обратиться к объекту ClassLoader, используйте метод getPackages() и создайте свой собственный итератор иерархии пакетов.

Просто предупреждение, что эти методы и классы могут быть довольно медленными.

Другие вопросы по тегам