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

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

Ответ 1

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

  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, которые всегда будут ограничены классами, которые уже были загружены, если только вы не выполните поиск по каталогу.

Ответ 2

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

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

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

Ответ 3

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

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

Механизм Service Provider - это обычное средство для перечисления реализаций подключаемой службы. Используйте ServiceLoader в Java 6 или реализуйте свой собственный в более ранних версиях. Я привел пример в другом ответе.

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

Новая версия @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();
    }   
}

Ответ 11

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