Skip to the content.

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:

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 to VertxImpl at the application startup stage, because CDI does not create a proxy bean for VertxImpl.

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.