1. Overview
1.概述
In this tutorial, we’ll go over an interesting feature of CDI (Context and Dependency Injection) called CDI portable extension.
在本教程中,我们将讨论CDI(上下文和依赖注入)的一个有趣的功能,即CDI可移植扩展。
First, we’ll start by understanding how it works, and then we’ll see how to write an extension. We’ll go through the steps to implement a CDI integration module for Flyway, so we can run a database migration on startup of a CDI container.
首先,我们先了解它的工作原理,然后再看如何写一个扩展。我们将经历为Flyway实现CDI集成模块的步骤,这样我们就可以在CDI容器的启动中运行数据库迁移。
This tutorial assumes a basic understanding of CDI. Have a look at this article for an introduction to CDI.
本教程假定对CDI有基本了解。请看这篇文章,了解CDI的介绍。
2. What Is a CDI Portable Extension?
2.什么是CDI便携式扩展?
A CDI portable extension is a mechanism by which we can implement additional functionalities on top of the CDI container. At the bootstrap time, the CDI container scans the classpath and creates metadata about the discovered classes.
CDI可移植扩展是一种机制,通过它我们可以在CDI容器之上实现额外的功能。在启动时,CDI容器会扫描classpath并创建关于所发现的类的元数据。
During this scanning process, the CDI container fires many initialization events which can be only observed by extensions. This is where a CDI portable extension comes into play.
在这个扫描过程中,CDI容器发射了许多初始化事件,这些事件只能由扩展来观察。这就是CDI便携扩展发挥作用的地方。
A CDI Portable extension observes these events and then modifies or adds information to the metadata created by the container.
CDI便携式扩展观察这些事件,然后修改或添加信息到由容器创建的元数据。
3. Maven Dependencies
3.Maven的依赖性
Let’s start by adding the required dependency for the CDI API in the pom.xml. It’s sufficient for implementing an empty extension.
让我们先在pom.xml中添加CDI API的必要依赖。这对于实现一个空的扩展是足够的。
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0.SP1</version>
</dependency>
And for running the application, we can use any compliant CDI implementation. In this article, we’ll use the Weld implementation.
而对于运行应用程序,我们可以使用任何符合要求的CDI实现。在这篇文章中,我们将使用Weld的实现。
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<version>3.0.5.Final</version>
<scope>runtime</scope>
</dependency>
You can check if any new versions of the API and the implementation have been released at Maven Central.
您可以在Maven中心查看API和实现的任何新版本是否已经发布。
4. Running Flyway in a Non-CDI Environment
4.在非CDI环境下运行Flyway
Before we start integrating Flyway and CDI, we should look first at how to run it in a non-CDI context.
在我们开始整合Flyway和CDI之前,我们应该先看看如何在非CDI环境下运行它。
So let’s take a look at the following example taken from the official site of Flyway:
因此,让我们看看下面的例子,它取自Flyway的官方网站:。
DataSource dataSource = //...
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.migrate();
As we can see, we’re only using a Flyway instance which needs a DataSource instance.
我们可以看到,我们只使用了一个Flyway实例,它需要一个DataSource实例。
Our CDI portable extension will later produce the Flyway and Datasource beans. For the purpose of this sample, we’ll use an embedded H2 database and we’ll provide DataSource properties through the DataSourceDefinition annotation.
我们的CDI可移植扩展随后将产生Flyway和DatasourceBean。在本示例中,我们将使用一个嵌入式H2数据库,并通过DataSourceDefinition注解提供DataSource属性。
5. CDI Container Initialization Events
5.CDI容器初始化事件
At the application bootstrap, the CDI container starts by loading and instantiating all CDI portable extensions. Then, in each extension, it searches and registers observer methods of initialization events if any. After that, it performs the following steps:
在应用启动时,CDI容器开始加载和实例化所有CDI可移植扩展。然后,在每个扩展中,它搜索并注册初始化事件的观察者方法(如果有)。之后,它执行以下步骤。
- Fires BeforeBeanDiscovery event before the scanning process begins
- Performs the type discovery in which it scans archive beans, and for each discovered type it fires the ProcessAnnotatedType event
- Fires the AfterTypeDiscovery event
- Performs the bean discovery
- Fires the AfterBeanDiscovery event
- Performs bean validation and detect definition errors
- Fires the AfterDeploymentValidation event
The intention of a CDI portable extension is then to observe these events, check metadata about the discovered beans, modify this metadata or add to it.
CDI便携扩展的目的是观察这些事件,检查关于被发现的Bean的元数据,修改这些元数据或添加到其中。
In a CDI portable extension, we can only observe these events.
在CDI便携式扩展中,我们只能观察到这些事件。
6. Writing the CDI Portable Extension
6.编写CDI便携式扩展
Let’s see how we can hook into some of these events by building our own CDI portable extension.
让我们看看我们如何通过建立我们自己的CDI可移植扩展来钩住其中的一些事件。
6.1. Implementing the SPI Provider
6.1.实施SPI供应商
A CDI portable extension is a Java SPI provider of the interface javax.enterprise.inject.spi.Extension. Have a look at this article for an introduction to Java SPI.
CDI可移植扩展是接口javax.enterprise.inject.spi.Extension.的Java SPI提供者,请看本文对Java SPI的介绍。
First, we start by providing the Extension implementation. Later, we’ll add observer methods to the CDI container bootstrap events:
首先,我们从提供Extension实现开始。稍后,我们将为CDI容器的引导事件添加观察者方法。
public class FlywayExtension implements Extension {
}
Then, we add a file name META-INF/services/javax.enterprise.inject.spi.Extension with this content:
然后,我们添加一个文件名META-INF/services/javax.enterprise.injective.spi.Extension,其中包含这个内容。
com.baeldung.cdi.extension.FlywayExtension
As an SPI, this Extension is loaded before the container bootstrap. So observer methods on the CDI bootstrap events can be registered.
作为一个SPI,这个Extension在容器引导之前被加载。所以可以注册CDI引导事件的观察者方法。
6.2. Defining Observer Methods of Initialization Events
6.2.定义初始化事件的观察者方法
In this example, we make the Flyway class known to the CDI container before the scanning process begins. This is done in the registerFlywayType() observer method:
在这个例子中,我们在扫描过程开始前让CDI容器知道Flyway类。这是在registerFlywayType()观察者方法中完成的。
public void registerFlywayType(
@Observes BeforeBeanDiscovery bbdEvent) {
bbdEvent.addAnnotatedType(
Flyway.class, Flyway.class.getName());
}
Here, we have added metadata about the Flyway class. From now on, it’ll behave as if it was scanned by the container. For this purpose, we have used the addAnnotatedType() method.
在这里,我们已经添加了关于Flyway类的元数据。从现在开始,它将表现得像被容器扫描过一样。为此,我们使用了addAnnotatedType()方法。
Next, we’ll observe the ProcessAnnotatedType event to make the Flyway class as a CDI managed bean:
接下来,我们将观察ProcessAnnotatedType事件,使Flyway类成为CDI托管Bean。
public void processAnnotatedType(@Observes ProcessAnnotatedType<Flyway> patEvent) {
patEvent.configureAnnotatedType()
.add(ApplicationScoped.Literal.INSTANCE)
.add(new AnnotationLiteral<FlywayType>() {})
.filterMethods(annotatedMethod -> {
return annotatedMethod.getParameters().size() == 1
&& annotatedMethod.getParameters().get(0).getBaseType()
.equals(javax.sql.DataSource.class);
}).findFirst().get().add(InjectLiteral.INSTANCE);
}
First, we annotate the Flyway class with @ApplicationScoped and @FlywayType annotations, then we search the Flyway.setDataSource(DataSource dataSource) method and we annotate it by @Inject.
首先,我们用@ApplicationScoped和@FlywayType注解来注解Flyway.setDataSource(DataSource dataSource)方法,我们用@Inject.注解它。
The final result of the above operations have the same effect as if the container scans the following Flyway bean:
上述操作的最终结果与容器扫描以下Flywaybean的效果相同。
@ApplicationScoped
@FlywayType
public class Flyway {
//...
@Inject
public void setDataSource(DataSource dataSource) {
//...
}
}
The next step is then to make a DataSource bean available for injection as our Flyway bean depends on a DataSource bean.
下一步是使DataSource Bean可用于注入,因为我们的Flyway Bean依赖于DataSource Bean。
For that, we’ll process to register a DataSource Bean into the container and we’ll use the AfterBeanDiscovery event:
为此,我们将在容器中注册一个DataSource Bean,我们将使用AfterBeanDiscovery事件。
void afterBeanDiscovery(@Observes AfterBeanDiscovery abdEvent, BeanManager bm) {
abdEvent.addBean()
.types(javax.sql.DataSource.class, DataSource.class)
.qualifiers(new AnnotationLiteral<Default>() {}, new AnnotationLiteral<Any>() {})
.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.
我们可以看到,我们需要一个DataSourceDefinition来提供DataSource属性。
We can annotate any managed bean with the following annotation:
我们可以用以下注解来注解任何管理型Bean。
@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:
为了提取这些属性,我们观察ProcessAnnotatedType事件以及@WithAnnotations注释。
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:
最后,我们监听AfterDeployementValidation事件,以从CDI容器中获取想要的Flywaybean,然后调用migrate()方法。
void runFlywayMigration(
@Observes AfterDeploymentValidation adv,
BeanManager manager) {
Flyway flyway = manager.createInstance()
.select(Flyway.class, new AnnotationLiteral<FlywayType>() {}).get();
flyway.migrate();
}
7. Conclusion
7.结语
Building a CDI portable extension seems difficult at the first time, but once we understand the container initialization lifecycle and the SPI dedicated to extensions, it becomes a very powerful tool that we can use to build frameworks on top of Jakarta EE.
构建一个CDI可移植扩展在最初的时候似乎很困难,但是一旦我们理解了容器初始化生命周期和扩展专用的SPI,它就成为一个非常强大的工具,我们可以用它来在Jakarta EE之上构建框架。
As usual, all the code samples shown in this article can be found over on GitHub.
像往常一样,本文中显示的所有代码样本都可以在GitHub上找到over。