Як я можу отримати список усіх реалізацій інтерфейсу програмно на Java? [зачинено]


81

Чи можу я це зробити з відображенням чи щось подібне?


Чи можна це зробити за допомогою якогось чарівного ярлика Eclipse? ?
Томаш Ващик

4
Будь ласка, розгляньте вибір відповіді.
Please_Dont_Bully_Me_SO_Lords

Відповіді:


58

Я шукав якийсь час, і, схоже, існують різні підходи, ось короткий виклад:

  1. бібліотека роздумів досить популярна, якщо ви не проти додати залежність. Це виглядало б так:

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

    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, які завжди будуть обмежені вже завантаженими класами, якщо ви не виконуєте пошук на основі каталогів.


Який із трьох підходів є більш ефективним та швидким?
carlspring

@carlspring Я не можу зробити абсолютну заяву про їх відносну ефективність, але перша альтернатива (бібліотека роздумів) спрацювала для мене досить добре.
Ахмад Абдельгані

Як працює DriverManager JDBC? Хіба це не робить подібну справу (пошук усіх реалізацій інтерфейсу драйвера в classpath)?
Олексій Семенюк

1
@AlexSemeniuk Я думаю, що зараз вони підтримують механізм завантажувача / постачальника послуг (підхід №2 вище) відповідно до їхніх документів "Методи DriverManager getConnection та getDrivers були вдосконалені для підтримки механізму Java Standard Edition Service Provider." Див. Docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ахмад Абдельгані

1
Варто зазначити, що механізм постачальника послуг був інтегрований в систему модулів з Java 9, що робить його навіть зручнішим, ніж анотації рівня пакета, тобто вам не потрібно створювати власний тип анотацій, може оголосити реалізації в модулі -інформацію, яку ви все одно створите під час написання модульного програмного забезпечення та отримаєте відгук про час компіляції щодо обгрунтованості реалізації.
Холгер

28

Що сказав Еріксон, але якщо ви все одно хочете це зробити, тоді погляньте на " Роздуми" . З їх сторінки:

За допомогою Reflections ви можете запитати свої метадані щодо:

  • отримати всі підтипи певного типу
  • отримати всі типи анотованих з деякими анотаціями
  • отримати всі типи анотованих з деякими анотаціями, включаючи відповідність параметрів анотації
  • отримати всі методи, анотовані деякими

12
більш конкретно:new Reflections("my.package").getSubTypesOf(MyInterface.class)
zapp

27

Загалом, робити це дорого. Щоб використовувати рефлексію, клас потрібно завантажити. Якщо ви хочете завантажити кожен клас, доступний на шляху до класу, це займе час і пам’ять, і не рекомендується.

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

Механізм постачальника послуг є звичайним засобом для перерахування реалізацій підключається служби і став більш усталеним завдяки введенню Jigsaw проекту (модулів) в Java 9. Використовуйте ServiceLoaderв Java 6 або впроваджуйте власні в попередніх версіях. Я подав приклад в іншій відповіді.


4
На жаль, механізм Постачальника послуг вимагає перерахування класів, які можуть бути у списку, що цікавить, в окремому файлі, що часто є нездійсненним.
averasko

4
Це можливо, щоб написати вихідний код, що реалізує інтерфейс, скомпілювати його та розгорнути, але неможливо включити текстовий файл із FQN реалізації? Це рідко, якщо взагалі колись, буває. Навряд чи "часто".
erickson,

Як працює DriverManager JDBC? Хіба це не робить подібну справу (пошук усіх реалізацій інтерфейсу драйвера в classpath)?
Олексій Семенюк

1
@AlexSemeniuk No. він використовує механізм Service Loader, який я описав вище; Драйвер JDBC 4.0+ повинен вказати своє ім'я вMETA-INF/services/java.sql.Driver
erickson

16

Весна має досить простий спосіб досягти цього:

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;
}

4
Не зовсім так, весна заповнить усі боби типу ITask, що не зовсім однаково.
Олів'є Жерарден

5

В даний час найнадійнішим механізмом для переліку всіх класів, що реалізують даний інтерфейс, є 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
    }
}

1
Бібліотека ClassGraph працює як шарм, дякую @Luke Hutchison
Кирило Семенко

4

Те, що сказав Еріксон, найкраще. Ось відповідна нитка запитань і відповідей - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

Бібліотека Apache BCEL дозволяє читати класи, не завантажуючи їх. Я вірю, що це буде швидше, тому що ви зможете пропустити крок перевірки. Інша проблема завантаження всіх класів за допомогою завантажувача класів полягає в тому, що ви зазнаєте величезного впливу на пам’ять, а також ненавмисно запустите будь-які статичні блоки коду, чого ви, мабуть, не хочете робити.

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


4

С ClassGraph це досить просто:

Код Groovy для пошуку реалізацій 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
}

Вам потрібно зателефонувати scan().withCloseable { ... }в Groovy або скористатися try-with-resources на Java: github.com/classgraph/classgraph/wiki/... Крім того, остання частина повинна бути .name, ні .className, оскільки .getName()це правильний метод для отримання імені класу від ClassInfoоб'єкта.
Luke Hutchison,


2

Нова версія відповіді @ kaybee99, але тепер повертається те, що запитує користувач: реалізації ...

Весна має досить простий спосіб досягти цього:

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();
    }   
}

1

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


1

Я натрапив на те саме питання. Моє рішення полягало у використанні відображення для вивчення всіх методів у класі ObjectFactory, усуваючи ті, які не були методами createXXX (), що повертають екземпляр одного з моїх пов'язаних POJO. Кожен виявлений таким чином клас додається до масиву Class [], який потім передається виклику екземпляру JAXBContext. Це працює добре, потрібно лише завантажити клас ObjectFactory, який і так мав знадобитися. Мені потрібно лише підтримувати клас ObjectFactory, завдання, яке виконується вручну (у моєму випадку, тому що я починав з POJO і використовував схему), або може бути згенеровано за потреби xjc. У будь-якому випадку, це ефективно, просто та ефективно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.