CDI преносимо разширение и Flyway

1. Общ преглед

В този урок ще разгледаме интересна функция на CDI (Контекст и инжекция на зависимост), наречена CDI преносимо разширение.

Първо ще започнем, като разберем как работи, а след това ще видим как да напишем разширение. Ще преминем през стъпките за внедряване на CDI интеграционен модул за Flyway, за да можем да стартираме миграция на база данни при стартиране на CDI контейнер.

Този урок предполага основно разбиране на CDI. Погледнете тази статия за въведение в CDI.

2. Какво е преносимо разширение CDI?

Преносимото разширение на CDI е механизъм, чрез който можем да внедрим допълнителни функции в горната част на CDI контейнера. По време на зареждане CDI контейнерът сканира пътя на класа и създава метаданни за откритите класове.

По време на този процес на сканиране CDI контейнерът задейства много събития за инициализация, които могат да бъдат наблюдавани само от разширения . Тук влиза в действие преносимото разширение CDI.

Преносимо разширение CDI наблюдава тези събития и след това модифицира или добавя информация към метаданните, създадени от контейнера.

3. Зависимости на Maven

Нека започнем с добавяне на необходимата зависимост за CDI API в pom.xml . Това е достатъчно за внедряване на празно разширение.

 javax.enterprise cdi-api 2.0.SP1 

И за стартиране на приложението можем да използваме всяко съвместимо CDI изпълнение. В тази статия ще използваме изпълнението на Weld.

 org.jboss.weld.se weld-se-core 3.0.5.Final runtime 

Можете да проверите дали в Maven Central са пуснати нови версии на API и внедряването.

4. Стартиране на Flyway в среда без CDI

Преди да започнем да интегрираме Flyway и CDI, първо трябва да разгледаме как да го стартираме в не-CDI контекст.

Така че нека да разгледаме следния пример, взет от официалния сайт на Flyway :

DataSource dataSource = //... Flyway flyway = new Flyway(); flyway.setDataSource(dataSource); flyway.migrate();

Както виждаме, използваме само екземпляр на Flyway, който се нуждае от екземпляр DataSource .

Нашето преносимо разширение CDI по-късно ще произведе зърната Flyway и Datasource . За целите на тази извадка, ще използваме вградена база данни, H2 и ще дадем DataSource имоти през DataSourceDefinition анотацията.

5. Събития за инициализация на CDI контейнери

При стартиране на приложението CDI контейнерът започва чрез зареждане и създаване на екземпляр на всички преносими разширения на CDI. След това във всяко разширение той търси и регистрира методи за наблюдение на събития за инициализация, ако има такива. След това той изпълнява следните стъпки:

  1. Пожари BeforeBeanDiscovery събитие, преди да започне процеса на сканиране
  2. Извършва откриването на типа, при което сканира архивни зърна и за всеки открит тип задейства събитието ProcessAnnotatedType
  3. Пожари на AfterTypeDiscovery събитието
  4. Извършва откриването на боб
  5. Пожари на AfterBeanDiscovery събитието
  6. Извършва проверка на боб и открива грешки в дефиницията
  7. Пожари на AfterDeploymentValidation събитието

След това намерението на преносимо разширение CDI е да наблюдава тези събития, да проверява метаданни за откритите зърна, да модифицира тези метаданни или да ги добавя.

В преносимо разширение CDI можем да наблюдаваме само тези събития.

6. Писане на преносимо разширение CDI

Нека видим как можем да се включим в някои от тези събития, като изградим наше собствено преносимо разширение CDI.

6.1. Внедряване на доставчика на SPI

Преносимото разширение CDI е доставчик на Java SPI на интерфейса javax.enterprise.inject.spi.Extention. Погледнете тази статия за въведение в Java SPI.

Първо, започваме с предоставяне на изпълнението на разширението . По-късно ще добавим методи за наблюдение към CDI събитията за зареждане на контейнера:

public class FlywayExtension implements Extension { }

След това добавяме име на файл META-INF / services / javax.enterprise.inject.spi.Extention с това съдържание:

com.baeldung.cdi.extension.FlywayExtension

Като SPI, това разширение се зарежда преди bootstrap на контейнера. Така че могат да бъдат регистрирани методи за наблюдение на CDI bootstrap събития.

6.2. Определяне на методи за наблюдение на събития за инициализация

В този пример правим класа Flyway известен на CDI контейнера, преди да започне процесът на сканиране. Това се прави в метода на наблюдател registerFlywayType () :

public void registerFlywayType( @Observes BeforeBeanDiscovery bbdEvent) { bbdEvent.addAnnotatedType( Flyway.class, Flyway.class.getName()); }

Тук сме добавили метаданни за класа Flyway . Отсега нататък той ще се държи така, сякаш е сканиран от контейнера. За тази цел използвахме метода addAnnotatedType () .

След това ще се наблюдава ProcessAnnotatedType събитието, за да се направи Понтика класа като CDI успя боб:

public void processAnnotatedType(@Observes ProcessAnnotatedType patEvent) { patEvent.configureAnnotatedType() .add(ApplicationScoped.Literal.INSTANCE) .add(new AnnotationLiteral() {}) .filterMethods(annotatedMethod -> { return annotatedMethod.getParameters().size() == 1 && annotatedMethod.getParameters().get(0).getBaseType() .equals(javax.sql.DataSource.class); }).findFirst().get().add(InjectLiteral.INSTANCE); }

Първо, ние вписва в Понтика класа с @ApplicationScoped и @FlywayType анотации, тогава ние търсене в Flyway.setDataSource (DataSource DATASOURCE) метода и ние го поясняват от @Inject.

Крайният резултат от горните операции има същия ефект, както ако контейнерът сканира следния боб Flyway :

@ApplicationScoped @FlywayType public class Flyway { //... @Inject public void setDataSource(DataSource dataSource) { //... } }

След това следващата стъпка е да направим боб DataSource на разположение за инжектиране, тъй като нашият боб Flyway зависи от боб DataSource .

For that, we'll process to register a DataSource Bean into the container and we'll use the AfterBeanDiscovery event:

void afterBeanDiscovery(@Observes AfterBeanDiscovery abdEvent, BeanManager bm) { abdEvent.addBean() .types(javax.sql.DataSource.class, DataSource.class) .qualifiers(new AnnotationLiteral() {}, new AnnotationLiteral() {}) .scope(ApplicationScoped.class) .name(DataSource.class.getName()) .beanClass(DataSource.class) .createWith(creationalContext -> { DataSource instance = new DataSource(); instance.setUrl(dataSourceDefinition.url()); instance.setDriverClassName(dataSourceDefinition.className()); return instance; }); }

As we can see, we need a DataSourceDefinition that provides the DataSource properties.

We can annotate any managed bean with the following annotation:

@DataSourceDefinition( name = "ds", className = "org.h2.Driver", url = "jdbc:h2:mem:testdb")

To extract these properties, we observe the ProcessAnnotatedType event along with the @WithAnnotations annotation:

public void detectDataSourceDefinition( @Observes @WithAnnotations(DataSourceDefinition.class) ProcessAnnotatedType patEvent) { AnnotatedType at = patEvent.getAnnotatedType(); dataSourceDefinition = at.getAnnotation(DataSourceDefinition.class); }

And finally, we listen to the AfterDeployementValidation event to get the wanted Flyway bean from the CDI container and then invoke the migrate() method:

void runFlywayMigration( @Observes AfterDeploymentValidation adv, BeanManager manager) { Flyway flyway = manager.createInstance() .select(Flyway.class, new AnnotationLiteral() {}).get(); flyway.migrate(); }

7. Conclusion

Изграждането на преносимо разширение за CDI изглежда трудно за първи път, но след като разберем жизнения цикъл на инициализацията на контейнера и SPI, посветен на разширенията, това се превръща в много мощен инструмент, който можем да използваме за изграждане на рамки върху Jakarta EE.

Както обикновено, всички примерни кодове, показани в тази статия, могат да бъдат намерени в GitHub.