Під час виконання знайдіть усі класи в додатку Java, які розширюють базовий клас


147

Я хочу зробити щось подібне:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Animal)
      animals.add( new c() );

Отже, я хочу переглянути всі класи у Всесвіті моєї програми, і коли я знайду такий, який походить від Animal, я хочу створити новий об’єкт цього типу та додати його до списку. Це дозволяє мені додавати функціональність без необхідності оновлювати список речей. Я можу уникати наступного:

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

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

ОНОВЛЕННЯ: 16.10.2008 9:00 за тихоокеанським стандартним часом:

Це питання породило багато чудових відповідей - дякую. З відповідей та моїх досліджень я з’ясував, що те, що я дійсно хочу зробити, просто не можливо під Java. Є такі підходи, як механізм ServiceLoader ddimitrov, який може працювати - але вони дуже важкі для того, що я хочу, і я вважаю, що я просто переміщу проблему з коду Java у зовнішній файл конфігурації. Оновлення 5/10/19 (11 років по тому!) Є тепер кілька бібліотек , які можуть допомогти з цим в відповідно до @ IvanNik в відповідь org.reflections виглядає добре. Також ClassGraph від @Luke Хатчінсон відповіді виглядає цікаво. Є ще кілька можливостей у відповідях.

Ще один спосіб заявити, що я хочу: статична функція в моєму класі Animal знаходить і створює всі класи, які успадковуються від Animal - без додаткової конфігурації / кодування. Якщо мені доведеться конфігурувати, я все-таки міг би просто заповнити їх у класі Animal. Я розумію, що програма Java - це просто вільна федерація файлів .class, що це саме так.

Цікаво, що здається, що це досить тривіально в C #.


1
Після деякого пошуку, здається, що це важкий горіх зламати на Яві. Ось нитка, яка містить деяку інформацію: forums.sun.com/thread.jspa?threadID=341935&start=15 Реалізацій в ній більше, ніж мені потрібно, я думаю, я просто дотримуюся другої реалізації на даний момент.
JohnnyLambada

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

3
Посилання на це в C # не показує нічого особливого. Сборка AC # еквівалентна файлу Java JAR. Це колекція складених файлів класу (та ресурсів). Посилання показує, як винести класи з однієї збірки. Ви можете це зробити так само легко з Java. Проблема для вас полягає в тому, що вам потрібно переглянути всі файли JAR та справжні вільні файли (каталоги); вам потрібно було б зробити те ж саме з .NET (шукати якусь ПАТ чи якусь різновид).
Кевін Брок

Відповіді:


156

Я використовую org.reflections :

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);

Ще один приклад:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}

6
Дякуємо за посилання про org.reflections. Я помилково вважав, що це буде так просто, як це є у світі .Net, і ця відповідь заощадила мені багато часу.
akmad

1
Дякую дійсно за це корисне посилання на чудовий пакет "org.reflections"! З цим я нарешті знайшов практичне і акуратне рішення для своєї проблеми.
Хартмут П.

3
Чи є спосіб отримати всі роздуми, тобто, не зазначаючи конкретний пакет, але завантажуючи всі наявні класи?
Joey Baruch

Пам’ятайте, що пошук класів не вирішить вашу проблему з інстанціюванням (рядок 5 animals.add( new c() );вашого коду), оскільки, хоча Class.newInstance()існує, у вас немає гарантії, що конкретний клас має конструктор без параметрів (C # дозволяє вимагати цього в загальному вигляді)
usr-local-ΕΨΗΕΛΩΝ

Дивіться також ClassGraph: github.com/classgraph/classgraph (Відмова, я автор). Я приведу приклад коду в окремій відповіді.
Люк Хатчісон

34

Спосіб Java зробити те, що ви хочете, - це використовувати механізм ServiceLoader .

Також багато людей згортають їх, маючи файл у добре відомій місцевості classpath (тобто /META-INF/services/myplugin.properties), а потім за допомогою ClassLoader.getResources () перераховувати всі файли з цим ім'ям з усіх банок. Це дозволяє кожній банці експортувати власні провайдери, і ви можете інстанціювати їх за допомогою відображення за допомогою Class.forName ()


1
Щоб легко створити файл служб META-INF на основі анотацій про класи, ви можете перевірити: metainf-services.kohsuke.org або code.google.com/p/spi
elek

Блея. Просто додає рівень складності, але не виключає необхідності створити клас, а потім зареєструвати його в далекій далекій землі.
Кевін Клайн

Будь ласка, дивіться відповідь нижче з 26 відгуками, набагато краще
SobiborTreblinka

4
Дійсно, якщо вам потрібні типові робочі рішення, Роздуми / Сканування є хорошим варіантом, але обидва нав'язують зовнішню залежність, не детерміновані і не підтримуються процесом спільноти Java. Крім того, я хотів би підкреслити, що ви ніколи не можете надійно отримати ВСІ класи, що реалізують інтерфейс, оскільки виготовити нове з повітря можна будь-коли. Попри всі бородавки, службовий завантажувач Java простий у розумінні та використанні. Я хотів би триматися подалі від нього з різних причин, але сканування шляху до класів ще гірше: stackoverflow.com/a/7237152/18187
ddimitrov

11

Подумайте про це з аспектно-орієнтованої точки зору; що ви хочете зробити, насправді, це знати всі класи під час виконання, які HAVE продовжили клас Animal. (Я думаю, що це дещо точніший опис вашої проблеми, ніж ваша назва; інакше я не думаю, що у вас є питання виконання.)

Отже, я думаю, що ви хочете, це створити конструктор базового класу (Animal), який додасть до вашого статичного масиву (я віддаю перевагу ArrayLists, а сам кожному, але кожному) свій тип поточного класу, який зараз інстанціюється.

Отже, приблизно;

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

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


6

На жаль, це не цілком можливо, оскільки ClassLoader не скаже вам, які класи доступні. Однак ви можете досить близько зробити щось подібне:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);

        JarInputStream is = new JarInputStream(new FileInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                //   for implementation of the interface
            }
        }
    }
}

Редагувати: johnstok вірно (у коментарях), що це працює лише для автономних додатків Java і не працюватиме на сервері додатків.


Це не працює добре, коли класи завантажуються іншими способами, наприклад, у веб-або J2EE-контейнері.
johnstok

Ви, мабуть, могли б зробити кращу роботу навіть під контейнером, якщо ви запитували шляхи (часто, URL[]але не завжди, так що це не можливо) з ієрархії ClassLoader. Часто, хоча ви повинні мати дозвіл, оскільки зазвичай у вас буде SecurityManagerзавантажений JVM.
Кевін Брок

Працювало як шарм для мене! Я побоювався, що це буде занадто великою вагою і повільним проходженням усіх класів на classpath, але насправді я обмежив файли, які я перевірив, на ті, що містять "-ejb-" або інші впізнавані імена файлів, і це все ще в 100 разів швидше, ніж запуск вбудована скляна рибка або контейнер EJBContainer. Тоді в моїй конкретній ситуації я проаналізував класи, шукаючи анотації "Без громадянства", "Станства" та "Синглтон", і якщо я їх побачив, я додав ім'я Maped JNPI до свого MockInitialContextFactory. Знову дякую!
cs94njw

4

Ви можете використовувати ResolverUtil ( вихідне джерело ) з Stripes Framework,
якщо вам потрібно щось просте і швидке, не переробляючи жодного наявного коду.

Ось простий приклад не завантаження жодного з класів:

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

Це також працює на сервері прикладних програм, оскільки саме там він був розроблений для роботи;)

Основний код:

  • ітерайте всі вказані вами ресурси
  • зберігайте лише ресурси, що закінчуються .class
  • Завантажте ці класи за допомогою ClassLoader#loadClass(String fullyQualifiedName)
  • Перевіряє, чи є Animal.class.isAssignableFrom(loadedClass);

4

Найбільш надійний механізм для перерахування всіх підкласів даного класу на даний момент ClassGraph , оскільки він обробляє максимально широкий масив механізмів специфікації classpath , включаючи нову модульну систему JPMS. (Я автор.)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

Результатом є список типу <Клас <Animal>>
hiaclibe

2

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


2

використовуй це

public static Set<Class> getExtendedClasses(Class superClass)
{
    try
    {
        ResolverUtil resolver = new ResolverUtil();
        resolver.findImplementations(superClass, superClass.getPackage().getName());
        return resolver.getClasses();  
    }
    catch(Exception e)
    {Log.d("Log:", " Err: getExtendedClasses() ");}

    return null;
}

getExtendedClasses(Animals.class);

Редагувати:


2
Вам потрібно буде сказати трохи більше про ResolverUtil. Що це? З якої бібліотеки він походить?
JohnnyLambada

1

Дякую всім, хто відповів на це питання.

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

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

Схоже, що Java просто не налаштована на саморозкриття таким, яким є C #. Я припускаю, що проблема полягає в тому, що оскільки програма Java - це лише колекція файлів .class, що знаходиться десь у каталозі / файлі jar, час виконання не знає про клас, поки на нього не посилається. У той час завантажувач завантажує його - те, що я намагаюся зробити, це виявити, перш ніж посилатись на нього, що неможливо, не виходячи до файлової системи та дивлячись.

Мені завжди подобається код, який може виявити себе замість того, щоб мені розповідати про себе, але, на жаль, це також працює.

Знову дякую!


1
Java створена для самовідкриття. Вам просто потрібно зробити трохи більше роботи. Детальну інформацію див. У моїй відповіді.
Дейв Джарвіс

1

За допомогою OpenPojo ви можете зробити наступне:

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}

0

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


0

Один із способів - це змусити класи використовувати статичні ініціалізатори ... Я не думаю, що вони передаються у спадок (це не спрацює, якщо вони є):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

Це вимагає від вас додати цей код до всіх занять. Але це дозволяє уникнути десь великого потворного циклу, перевіряючи кожен клас, який шукає дітей Animal.


3
У моєму випадку це не працює. Оскільки клас ніде не посилається, він ніколи не завантажується, тому статичний ініціалізатор ніколи не викликається. Здається, що статичний ініціалізатор викликається перед усім іншим при завантаженні класу - але в моєму випадку клас ніколи не завантажується.
JohnnyLambada

0

Я вирішив цю проблему досить елегантно, використовуючи Анотації на рівні пакетів, а потім зробивши цю анотацію як аргумент списком класів.

Знайдіть класи Java, що реалізують інтерфейс

Реалізація просто повинна створити package-info.java і помістити чарівну примітку зі списком класів, які вони хочуть підтримувати.

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