Автоматический вызов статического блока без явного вызова Class.forName - PullRequest
0 голосов
/ 14 октября 2018

Возьмем следующий код:

public class Main {

    public static final List<Object> configuration = new ArrayList<>();

    public static void main(String[] args) {
        System.out.println(configuration);
    }
}

Теперь я хочу иметь возможность предоставлять «самоконфигурируемые» классы.Это означает, что они должны иметь возможность просто предоставить что-то вроде статического блока, который будет вызываться автоматически следующим образом:

public class Custom {
    static {
        Main.configuration.add(Custom.class);
    }
}

Если вы выполните этот код, список конфигурации будет пуст (из-за способ выполнения статических блоков ).Класс "достижим", , но не "загружен" .Вы можете добавить следующее к классу Main перед System.out

Class.forName("Custom");

, и теперь список будет содержать объект класса Custom (поскольку класс еще не инициализирован, этот вызов инициализирует его).Но поскольку элемент управления должен быть обратным (пользовательский интерфейс должен знать Main, а не наоборот), этот подход непригоден для использования.Custom никогда не следует вызывать напрямую из Main или любого другого класса, связанного с Main.

Хотя это возможно, можно сделать следующее: вы можете добавить аннотацию к классу и собрать все классы с указанной аннотацией, используячто-то вроде ClassGraph Framework и вызов Class.forName для каждого из них.

TL; DR

Есть ли способ автоматического вызова статического блока без необходимостипроанализировать все классы и необходимость знания конкретного, «самоконфигурируемого» класса?Идеальный подход - это подход, который при запуске приложения автоматически инициализирует классы (если они снабжены определенной аннотацией).Я думал о пользовательских ClassLoaders, но из того, что я понимаю, они ленивы и поэтому не могут использоваться для этого подхода.

Подоплекой этого является то, что я хочу включить его в аннотациюпроцессор, который создает «самоконфигурируемый код».

Пример ( предупреждение : дизайн-разговор и углубленно)

Чтобы сделать это немного менее абстрактным, представьте следующее:

Вы разрабатываете Framework.Давайте назовем это Foo.Foo имеет классы GlobalRepository и Repository.GlobalRepository следует шаблону проектирования Singleton (только статические методы).Репозиторий, как и GlobalRepository, имеет методы «void add (Object)» и «T get (Class)».Если вы вызываете get в репозитории и класс не может быть найден, он вызывает GlobalRepository.get (Class).

Для удобства вы хотите предоставить аннотацию с именем @Add.Эта аннотация может быть размещена в объявлениях типов (или классах).Процессор аннотаций создает некоторые конфигурации, которые автоматически добавляют все аннотированные классы в GlobalRepository и, следовательно, сокращают стандартный код.Это должно произойти только (во всех случаях) один раз.Для этого в сгенерированном коде есть статический инициализатор, в котором заполнен глобальный репозиторий, как если бы вы делали это с локальным репозиторием.Поскольку ваши Конфигурации имеют имена, которые спроектированы настолько уникальными, насколько это возможно, и по какой-то причине даже содержат дату создания (это немного произвольно, но оставайтесь со мной), их почти невозможно угадать.

Таким образом, вы также добавляете аннотацию к этим Конфигурациям, которая называется @AutoLoad.Требуется, чтобы использующий разработчик вызвал GlobalRepository.load (), после чего все классы анализируются, и все классы с этой аннотацией инициализируются, и для этого вызываются их соответствующие статические блоки.

Это не очень масштабируемыйподход.Чем больше приложение, тем больше область поиска, тем дольше время и т. Д.Лучше было бы, чтобы при запуске приложения все классы автоматически инициализировались.Как через ClassLoader.Что-то вроде этого - то, что я ищу.

1 Ответ

0 голосов
/ 17 октября 2018

Во-первых, не храните Class объекты в вашем реестре.Эти Class объекты потребовали бы, чтобы вы использовали Reflection для получения фактической операции, например, для их создания или вызова определенных методов, чью сигнатуру вы все равно должны знать заранее.

Стандартный подход заключается в использовании interface для описания операций, которые должны поддерживать динамические компоненты.Затем создайте реестр экземпляров реализации.Они по-прежнему позволяют отложить дорогостоящие операции, если вы разделите их на операционный интерфейс и заводской интерфейс.

Например, CharsetProvider не является действительным Charset реализация, но предоставляет доступ к ним по запросу.Таким образом, существующий реестр поставщиков не потребляет много памяти, если используются только общие кодировки.

После того, как вы определили такой интерфейс службы, вы можете использовать стандартный механизм обнаружения служб.В случае файлов JAR или каталогов, содержащих файлы классов, вы создаете подкаталог META-INF/services/, содержащий имя файла в качестве квалифицированного имени интерфейса, содержащего квалифицированные имена классов реализации.У каждой записи пути к классу может быть такой ресурс.

В случае модулей Java вы можете объявить такую ​​реализацию еще более надежной, используя операторы

provides service.interface.name with actual.implementation.class;

в объявлении вашего модуля.

Тогда основной класс может искать реализации, только зная интерфейс, как

List<MyService> registered = new ArrayList<>();
for(Iterator<MyService> i = ServiceLoader.load(MyService.class); i.hasNext();) {
    registered.add(i.next());
}

или, начиная с Java 9

List<MyService> registered = ServiceLoader.load(MyService.class)
    .stream().collect(Collectors.toList());

Документация класса из ServiceLoader содержит гораздо больше подробностей об этой архитектуре.Когда вы просматриваете список пакетов стандартного API в поисках пакетов, имена которых заканчиваются на .spi, вы понимаете, как часто этот механизм уже используется в самом JDK.Интерфейсы не обязательно должны быть в пакетах с такими именами, хотя, например, реализации java.sql.Driver также ищутся с помощью этого механизма.

Начиная с Java 9, вы можете даже использовать это, чтобы сделать что-то вроде «поискаClass объекты для всех классов, имеющих определенную аннотацию », например,

List<Class<?>> configuration = ServiceLoader.load(MyService.class)
    .stream()
    .map(ServiceLoader.Provider::type)
    .filter(c -> c.isAnnotationPresent(MyAnnotation.class))
    .collect(Collectors.toList());

, но так как это все еще требует, чтобы классы реализовывали интерфейс службы и были объявлены как реализации интерфейса, предпочтительно использовать методыобъявлен интерфейсом для взаимодействия с модулями.

...