Как я могу получить список всех программных реализаций интерфейса на Java?

Могу я сделать это с отражением или что-то в этом роде?


person user2427    schedule 07.12.2008    source источник
comment
Можно ли это сделать каким-нибудь волшебным ярлыком Eclipse? ?   -  person Tomasz Waszczyk    schedule 29.08.2014
comment
Пожалуйста, подумайте о выборе ответа.   -  person Please_Dont_Bully_Me_SO_Lords    schedule 14.12.2018


Ответы (11)


Я искал какое-то время, и, похоже, есть разные подходы, вот краткое изложение:

  1. Библиотека размышлений довольно популярна, если вы не против добавления зависимости. Это выглядело бы так:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (согласно ответу Эриксона), и он будет выглядеть так:

    ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    

    Обратите внимание, что для этого вам необходимо определить Pet как ServiceProviderInterface (SPI) и объявить его реализации. вы делаете это, создавая файл в resources/META-INF/services с именем examples.reflections.Pet и объявляя в нем все реализации Pet

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. аннотация на уровне пакета. вот пример:

    Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    

    и определение аннотации:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    

    и вы должны объявить аннотацию уровня пакета в файле с именем package-info.java внутри этого пакета. вот пример содержания:

    @MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    

    Обратите внимание, что только пакеты, которые известны ClassLoader в это время, будут загружены вызовом Package.getPackages().

Кроме того, существуют другие подходы, основанные на URLClassLoader, которые всегда будут ограничены классами, которые уже были загружены, если вы не выполните поиск по каталогам.

person Ahmad Abdelghany    schedule 25.05.2015
comment
Какой из трех подходов эффективнее и быстрее всего? - person carlspring; 29.05.2016
comment
@carlspring Я не могу однозначно сказать об их относительной эффективности, но первая альтернатива (библиотека отражений) мне очень понравилась. - person Ahmad Abdelghany; 30.05.2016
comment
Как работает DriverManager JDBC? Разве это не то же самое (поиск всех реализаций интерфейса драйвера в пути к классам)? - person Alex Semeniuk; 17.02.2019
comment
@AlexSemeniuk Я думаю, что теперь они поддерживают механизм Service Loader / Provider (подход № 2 выше) в соответствии с их документами. Методы DriverManager getConnection и getDrivers были улучшены для поддержки механизма Java Standard Edition Service Provider. См. docs.oracle.com/javase/8/docs /api/java/sql/DriverManager.html - person Ahmad Abdelghany; 18.02.2019
comment
Стоит отметить, что механизм поставщика услуг был интегрирован в модульную систему с Java 9, что делает его даже более удобным, чем аннотации уровня пакета, т.е. вам не нужно создавать свой собственный тип аннотации, можно объявлять реализации в модуле -info вы в любом случае создадите при написании модульного программного обеспечения и получите обратную связь во время компиляции относительно достоверности реализации. - person Holger; 04.03.2020

Что сказал Эриксон, но если вы все еще хотите это сделать, взгляните на Размышления. Со своей страницы:

Используя Reflections, вы можете запрашивать свои метаданные для:

  • получить все подтипы какого-либо типа
  • аннотировать все типы аннотациями
  • получить все типы, аннотированные некоторой аннотацией, включая соответствие параметров аннотации
  • аннотировать все методы некоторыми
person Peter Severin    schedule 07.12.2008
comment
более конкретно: new Reflections("my.package").getSubTypesOf(MyInterface.class) - person zapp; 16.03.2013

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

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

механизм поставщика услуг - это обычное средство для перечисления реализаций подключаемый сервис, который стал более популярным с появлением Project Jigsaw (модулей) в Java 9. Используйте _ 1_ в Java 6 или реализовать свой собственный в более ранних версиях. В другом ответе я привел пример.

person erickson    schedule 07.12.2008
comment
К сожалению, механизм поставщика услуг требует, чтобы вы перечислили классы, которые могут быть в списке интересов, в отдельном файле, что часто невозможно. - person averasko; 31.05.2015
comment
Возможно написать исходный код, реализующий интерфейс, скомпилировать его и развернуть, но невозможно включить текстовый файл с FQN реализации? Так бывает редко, если вообще случается. Вряд ли. - person erickson; 12.03.2017
comment
Как работает DriverManager JDBC? Разве это не то же самое (поиск всех реализаций интерфейса драйвера в пути к классам)? - person Alex Semeniuk; 17.02.2019
comment
@AlexSemeniuk Нет. Он использует механизм Service Loader, который я описал выше; Драйвер JDBC 4.0+ должен указать свое имя в META-INF/services/java.sql.Driver - person erickson; 17.02.2019

У Spring есть довольно простой способ добиться этого:

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Затем вы можете автоматически подключить список типа ITask, и Spring заполнит его всеми реализациями:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
person kaybee99    schedule 14.08.2017
comment
Не совсем то, что Spring заполнит его всеми beans типа ITask, что не совсем то же самое. - person Olivier Gérardin; 06.11.2018

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

try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
        foundImplementingClass(ci);  // Do something with the ClassInfo object
    }
}
person Luke Hutchison    schedule 01.08.2018
comment
Библиотека ClassGraph работает как шарм, спасибо @Luke Hutchison - person Kyrylo Semenko; 01.01.2020

С ClassGraph это довольно просто:

Отличный код для поиска реализаций my.package.MyInterface:

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
    scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
person verglor    schedule 20.01.2019
comment
Вам нужно вызвать scan().withCloseable { ... } в Groovy или использовать try-with-resources в Java: github.com/classgraph/classgraph/wiki/ Кроме того, последняя часть должна быть .name, а не .className, поскольку .getName() - это правильный метод для получения имени класса из ClassInfo объекта. - person Luke Hutchison; 05.10.2019

Лучше всего то, что сказал Эриксон. Вот связанная ветка вопросов и ответов - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

Библиотека Apache BCEL позволяет вам читать классы, не загружая их. Я считаю, что это будет быстрее, потому что вы сможете пропустить этап проверки. Другая проблема с загрузкой всех классов с помощью загрузчика классов заключается в том, что вы столкнетесь с огромным воздействием на память, а также случайно запустите любые статические блоки кода, которые вы, вероятно, не захотите делать.

Ссылка на библиотеку Apache BCEL - http://jakarta.apache.org/bcel/

person Community    schedule 07.12.2008

Да, первый шаг - определить все классы, которые вам небезразличны. Если у вас уже есть эта информация, вы можете перечислить каждый из них и использовать instanceof для проверки связи. Соответствующая статья находится здесь: https://web.archive.org/web/20100226233915/www.javaworld.com/javaworld/javatips/jw-javatip113.html

person Zhichao    schedule 07.12.2008

Новая версия ответа @ kaybee99, но теперь возвращает то, что спрашивает пользователь: реализации ...

У Spring есть довольно простой способ добиться этого:

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Затем вы можете автоматически подключить список типа ITask, и Spring заполнит его всеми реализациями:

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}
person Please_Dont_Bully_Me_SO_Lords    schedule 14.12.2018

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

person Uri    schedule 07.12.2008

Я столкнулся с той же проблемой. Мое решение состояло в том, чтобы использовать отражение для проверки всех методов в классе ObjectFactory, исключив те, которые не были методами createXXX (), возвращающими экземпляр одного из моих связанных POJO. Каждый обнаруженный таким образом класс добавляется в массив Class [], который затем передается в вызов создания экземпляра JAXBContext. Это работает хорошо, нужно только загрузить класс ObjectFactory, который в любом случае должен был понадобиться. Мне нужно только поддерживать класс ObjectFactory, задача, выполняемая вручную (в моем случае, потому что я начал с POJO и использовал schemagen), или может быть сгенерирована при необходимости с помощью xjc. В любом случае, это производительно, просто и эффективно.

person NDK    schedule 02.04.2017