Могу я сделать это с отражением или что-то в этом роде?
Как я могу получить список всех программных реализаций интерфейса на Java?
Ответы (11)
Я искал какое-то время, и, похоже, есть разные подходы, вот краткое изложение:
Библиотека размышлений довольно популярна, если вы не против добавления зависимости. Это выглядело бы так:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections"); Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
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
аннотация на уровне пакета. вот пример:
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, которые всегда будут ограничены классами, которые уже были загружены, если вы не выполните поиск по каталогам.
Что сказал Эриксон, но если вы все еще хотите это сделать, взгляните на Размышления. Со своей страницы:
Используя Reflections, вы можете запрашивать свои метаданные для:
- получить все подтипы какого-либо типа
- аннотировать все типы аннотациями
- получить все типы, аннотированные некоторой аннотацией, включая соответствие параметров аннотации
- аннотировать все методы некоторыми
new Reflections("my.package").getSubTypesOf(MyInterface.class)
- person zapp; 16.03.2013
В общем, делать это дорого. Чтобы использовать отражение, класс должен быть загружен. Если вы хотите загрузить каждый класс, доступный в пути к классам, это потребует времени и памяти и не рекомендуется.
Если вы хотите избежать этого, вам нужно будет реализовать собственный синтаксический анализатор файла класса, который работал бы более эффективно, вместо отражения. Библиотека инженерии байтового кода может помочь в этом подходе.
механизм поставщика услуг - это обычное средство для перечисления реализаций подключаемый сервис, который стал более популярным с появлением Project Jigsaw (модулей) в Java 9. Используйте _ 1_ в Java 6 или реализовать свой собственный в более ранних версиях. В другом ответе я привел пример.
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;
}
Наиболее надежным механизмом для перечисления всех классов, реализующих данный интерфейс, в настоящее время является 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
}
}
С 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
}
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/
Да, первый шаг - определить все классы, которые вам небезразличны. Если у вас уже есть эта информация, вы можете перечислить каждый из них и использовать instanceof для проверки связи. Соответствующая статья находится здесь: https://web.archive.org/web/20100226233915/www.javaworld.com/javaworld/javatips/jw-javatip113.html
Новая версия ответа @ 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();
}
}
Кроме того, если вы пишете плагин IDE (где то, что вы пытаетесь сделать, является относительно обычным), тогда IDE обычно предлагает вам более эффективные способы доступа к иерархии классов текущего состояния пользовательского кода.
Я столкнулся с той же проблемой. Мое решение состояло в том, чтобы использовать отражение для проверки всех методов в классе ObjectFactory, исключив те, которые не были методами createXXX (), возвращающими экземпляр одного из моих связанных POJO. Каждый обнаруженный таким образом класс добавляется в массив Class [], который затем передается в вызов создания экземпляра JAXBContext. Это работает хорошо, нужно только загрузить класс ObjectFactory, который в любом случае должен был понадобиться. Мне нужно только поддерживать класс ObjectFactory, задача, выполняемая вручную (в моем случае, потому что я начал с POJO и использовал schemagen), или может быть сгенерирована при необходимости с помощью xjc. В любом случае, это производительно, просто и эффективно.