Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Авторегистрация классов в Java

#java #java_ee


Имеется базовый класс (или интерфейс), от которого унаследованы другие классы. например, так:

class Basic { /* ... */ };
class Derived1 extends Basic { /* ... */ };
class Derived2 extends Basic { /* ... */ };
class Derived21 extends Derived2 { /* ... */ };
// . . .


Иерархия может быть сколь угодно большой и разветвлённой. Некоторые из этих классов
нужно занести в список класса-менеджера, который будет ими как-то управлять, например,
создавать экземпляры по запросу, выдавать клиентам список доступных им классов и т.п.

Собственно, вопрос. Каким образом "зарегистрировать" эти классы в менеджере? Ясно,
что можно сделать очень просто - занести их в код вручную. Но делать этого не хочется
потому, что иерархия меняется, причём меняется не одним человеком и велика вероятность
пропустить какой-либо класс. Интересует метод автоматического построения списка требуемых
классов.

Для решения задачи можно использовать любые конструкции языка Java (Java8 EE), генерировать
список можно как при компиляции, так и при выполнении. Но никакие внешние библиотеки
(в т.ч. Reflections) использовать нельзя. Максимум - встроенные библиотеки WildFly
10, на котором работает приложение.
    


Ответы

Ответ 1



В спецификации Servlet 3.0 появился интерфейс ServletContainerInitializer. У этого интерфейса есть метод void onStartup(Set> c, ServletContext ctx). Этот метод вызывается контейнером приложений (в моём случае WildFly) когда контейнер инициализирует приложение. У класса, реализующего данный интерфейс можно задать аннотацию @HandlesTypes, которая задаёт список классов/интерфейсов/аннотаций, в которых "заинтересована" конкретная реализация интерфейса. Этот список будет передан первым параметром метода onStartup. (Если аннотации @HandlesTypes нет, первый параметр будет равен null). Для решения задачи была создана собственная аннотация, которой помечаются требуемые классы. Реализация интерфейса ServletContainerInitializer объявляет "заинтересованность" в этой аннотации и в результате контейнер при инициализации приложения вызывает метод onStartup этой реализации, передав ему список помеченных классов. Ниже приведён пример кода. Файл src/main/java/so/kff/autoregister/TestAnnotation.java: package so.kff.autoregister; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // Аннотация сохраняется при выполнении. @Target(ElementType.TYPE) // Аннотация применяется только к классам. public @interface TestAnnotation { String value() default ""; } Реализации классов: package so.kff.autoregister; class Basic { /* ... */ }; @TestAnnotation class Derived1 extends Basic { /* ... */ }; class Derived2 extends Basic { /* ... */ }; @TestAnnotation class Derived21 extends Derived2 { /* ... */ }; // . . . Файл src/main/java/so/kff/autoregister/Manager.java: package so.kff.autoregister; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import java.util.Set; @HandlesTypes(TestAnnotation.class) public class Manager implements ServletContainerInitializer { @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { if (c != null) for (Class clazz : c) System.out.println(clazz.getName()); } } Чтобы контейнер запускал метод Manager.onStartup при инициализации, нужно создать файл src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer, в котором прописать полные имена классов, методы которых должны быть запущены. В нашем случае содержимое должно быть следующим: so.kff.autoregister.Manager Чтобы эта технология работала, реализация интерфейса ServletContainerInitializer должна лежать в каком-нибудь jar-файле, который, в свою очередь, вкладывается в секцию WEB-INF/lib развёртываемого приложения (war-файла). Собираем и разворачиваем наше приложение. В результате в консоли WildFly видим следующее: package so.kff.autoregister.Derived1; package so.kff.autoregister.Derived21; Тем самым классы мы получили. Дальше просто обрабатываем полученные классы так, как нам нужно.

Комментариев нет:

Отправить комментарий