Integrating Vertx application with Weld/CDI
In the last post, we introduced a simple approach to integrate Vertx applications with Spring framework. In this post, we will try to integrate Vertx application with CDI to replace Spring.
CDI is a Dependency and Injection specification which is introduced in Java EE 6, currently Java EE is renamed to Jakarta EE and maintained by Eclipse Foundation. Weld is a compatible provider of the Jakarta CDI specification.
Add the following dependencies.
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-shaded</artifactId>
<version>${weld.version}</version>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<version>2.2.3.Final</version>
</dependency>
In the above codes:
- The
weld-se-shaded
provides a CDI runtime environment for Java SE platform. - The
jandex
will index the classes in the application and speed up the bean searching.
Add an empty beans.xml
configuration in the main/resources/META-INF folder which is to enable CDI support in a Java SE application.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="annotated">
</beans>
In the Java EE/Jakarta EE world, CDI is enabled by default since Java EE 7, and the beans.xml configuration file is optional.
Similar to the Spring version, add a DemoApplication
to start the application.
public class DemoApplication {
private final static Logger LOGGER = Logger.getLogger(DemoApplication.class.getName());
public static void main(String[] args) {
var weld = new Weld();
var container = weld.initialize();
var vertx = container.select(Vertx.class).get();
var factory = container.select(VerticleFactory.class).get();
LOGGER.info("vertx clazz:" + vertx.getClass().getName());//Weld does not create proxy classes at runtime on @Singleton beans.
LOGGER.info("factory clazz:" + factory.getClass().getName());
// deploy MainVerticle via verticle identifier name
//var deployOptions = new DeploymentOptions().setInstances(4);
vertx.deployVerticle(factory.prefix() + ":" + MainVerticle.class.getName());
}
}
Here we uses weld.initialize()
to initialize the CDI container. Then retrieve the Vertx
bean and VerticleFactory
bean, and start to deploy the MainVerticle
.
Similar to the SpringAwareVerticleFactory
, create a CDI aware VerticleFactory
.
@ApplicationScoped
public class CdiAwareVerticleFactory implements VerticleFactory {
@Inject
private Instance<Object> instance;
@Override
public String prefix() {
return "cdi";
}
@Override
public void createVerticle(String verticleName, ClassLoader classLoader, Promise<Callable<Verticle>> promise) {
String clazz = VerticleFactory.removePrefix(verticleName);
promise.complete(() -> (Verticle) instance.select(Class.forName(clazz)).get());
}
}
Create a simple Resources
classes and expose the Vertx
and PgPool
beans.
@ApplicationScoped
public class Resources {
private final static Logger LOGGER = Logger.getLogger(Resources.class.getName());
@Produces
@Singleton
public Vertx vertx(VerticleFactory verticleFactory) {
Vertx vertx = Vertx.vertx();
vertx.registerVerticleFactory(verticleFactory);
return vertx;
}
@Produces
public PgPool pgPool(Vertx vertx) {
PgConnectOptions connectOptions = new PgConnectOptions()
.setPort(5432)
.setHost("localhost")
.setDatabase("blogdb")
.setUser("user")
.setPassword("password");
// Pool Options
PoolOptions poolOptions = new PoolOptions().setMaxSize(5);
// Create the pool from the data object
PgPool pool = PgPool.pool(vertx, connectOptions, poolOptions);
return pool;
}
public void disposesPgPool(@Disposes PgPool pgPool) {
LOGGER.info("disposing PgPool...");
pgPool.close().onSuccess(v -> LOGGER.info("PgPool is closed successfully."));
}
}
Other beans are similar to the Spring version, but using CDI @ApplicaitonScoped
to replace the Spring @Component
.
@ApplicationScoped
@RequiredArgsConstructor
public class MainVerticle extends AbstractVerticle {
final PostsHandler postHandlers;
//...
}
@ApplicationScoped
@RequiredArgsConstructor
class PostsHandler {
private final PostRepository posts;
//...
}
@ApplicationScoped
@RequiredArgsConstructor
public class PostRepository {
private final PgPool client;
//...
}
@ApplicationScoped
@RequiredArgsConstructor
public class DataInitializer {
private final PgPool client;
//...
}
Please note, in the above Resources
class, we add a @Singleton
to the Vertx
, which is a little different from the ApplicationScoped
, Weld does not create a proxy object for it.
Here we have to add
@Singleton
on Vertx bean, else there is an error of casting toVertxImpl
at the application startup stage, because CDI does not create a proxy bean forVertxImpl
.
In the DemoApplication
, we have added some log to print the class name of Vertx
and VerticleFactory
beans. When starting the application, in the console, you will see the class names as the following.
//..
INFO: vertx clazz:io.vertx.core.impl.VertxImpl
//...
INFO: factory clazz:com.example.demo.CdiAwareVerticleFactory$Proxy$_$$_WeldClientProxy
By default CDI will create proxy classes for all beans, but if it is annotated with @Singletone
, it will use the instance directly.
To test the application, add the following dependency to test scope.
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-junit5</artifactId>
<version>2.0.2.Final</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Similar to the Spring version, manually deploy the Verticle
in the JUnit @BeforeAll
hook before running tests.
@EnableAutoWeld
@AddPackages(DemoApplication.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(VertxExtension.class)
public class TestMainVerticle {
private final static Logger LOGGER = Logger.getLogger(TestMainVerticle.class.getName());
@Inject
Instance<Object> context;
Vertx vertx;
@BeforeAll
public void setupAll(VertxTestContext testContext) {
vertx = context.select(Vertx.class).get();
var factory = context.select(VerticleFactory.class).get();
vertx.deployVerticle(factory.prefix() + ":" + MainVerticle.class.getName())
.onSuccess(id -> {
LOGGER.info("deployed:" + id);
testContext.completeNow();
});
}
@Test
public void testVertx(VertxTestContext testContext) {
assertThat(vertx).isNotNull();
testContext.completeNow();
}
@Test
void testGetAll(VertxTestContext testContext) {
LOGGER.log(Level.INFO, "running test: {0}", "testGetAll");
var options = new HttpClientOptions()
.setDefaultPort(8888);
var client = vertx.createHttpClient(options);
client.request(HttpMethod.GET, "/posts")
.flatMap(req -> req.send().flatMap(HttpClientResponse::body))
.onSuccess(
buffer -> testContext.verify(
() -> {
LOGGER.log(Level.INFO, "response buffer: {0}", new Object[]{buffer.toString()});
assertThat(buffer.toJsonArray().size()).isGreaterThan(0);
testContext.completeNow();
}
)
)
.onFailure(e -> LOGGER.log(Level.ALL, "error: {0}", e.getMessage()));
}
}
Get the example codes from my github.