Skip to the content.

Integrating Vertx application with Spring framework

As shown in the former we assembled everything manually in the MainVerticle class.

For example, it looks like the following.

//Create a PgPool instance
var pgPool = pgPool();

//Creating PostRepository
var postRepository = PostRepository.create(pgPool);

//Creating PostHandler
var postHandlers = PostsHandler.create(postRepository);

// Initializing the sample data
var initializer = DataInitializer.create(pgPool);
initializer.run();

// Configure routes
var router = routes(postHandlers);

// Create the HTTP server
vertx.createHttpServer()...

In this post, we will introduce Spring framework to manage the dependencies of the above items.

Add the Spring context dependency into the project pom.xml file.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

The spring-context provides basic IOC functionality which is used for assembling dependencies and providing dependency injection.

Create a @Configuration class to start Spring application context.

@Configuration
@ComponentScan
public class DemoApplication {

    public static void main(String[] args) {
        var context = new AnnotationConfigApplicationContext(DemoApplication.class);
        var vertx = context.getBean(Vertx.class);
        var factory  = context.getBean(VerticleFactory.class);

        // deploy MainVerticle via verticle identifier name
        vertx.deployVerticle(factory.prefix()+":"+MainVerticle.class.getName());
    }

    @Bean
    public Vertx vertx(VerticleFactory verticleFactory) {
        Vertx vertx = Vertx.vertx();
        vertx.registerVerticleFactory(verticleFactory);
        return vertx;
    }

    @Bean
    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;
    }
}

In the main method, we use a AnnotationConfigApplicationContext to scan Spring components and assemble the dependencies. Then fetch Vertx bean and VerticleFactory bean from the Spring application context, and call vertx.deployVerticle.

The VerticleFactory is a Vertx built-in hook for instantiating a Verticle instance.

In this configuration, we also declares beans:

Let’s have a look at VerticleFactory bean - a Spring context aware VerticleFactory implementation.

@Component
public class SpringAwareVerticleFactory implements VerticleFactory, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public String prefix() {
        return "spring";
    }

    @Override
    public void createVerticle(String verticleName, ClassLoader classLoader, Promise<Callable<Verticle>> promise) {
        String clazz = VerticleFactory.removePrefix(verticleName);
        promise.complete(() -> (Verticle) applicationContext.getBean(Class.forName(clazz)));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

When instantiating a Verticle by name, it will call the createVerticle method, which looks up the beans in the Spring application context.

Let’s have a look at the other classes, which are declared as Spring @Component directly. For the complete source codes, check vertx-sandbox/post-service-spring from my Github.

@Component
@RequiredArgsConstructor
public class MainVerticle extends AbstractVerticle {
    final PostsHandler postHandlers;
    
    //...
}
@Component
@RequiredArgsConstructor
class PostsHandler {
    private final PostRepository posts;
    
    //...
}
@Component
@RequiredArgsConstructor
public class PostRepository {

    private final PgPool client;
    
    //...
}
@Component
@RequiredArgsConstructor
public class DataInitializer {

    private  final PgPool client;
    
    //...
}

All @Components can be scanned when DemoApplicaiton is running.

As you see, through Spring IOC container, we erase all manual steps of the assembling the dependencies.

Now let’s try to start the application.

In the former application, the application can be run by maven exec plugin and jar file. But it uses a built-in Launcher class to deploy the Verticle. We have configured to use Spring to complete the same work, so change the configuration of maven exec plugin and maven shade plugin to the following.

<plugin>
    <artifactId>maven-shade-plugin</artifactId>
    <version>${maven-shade-plugin.version}</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                                 implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Main-Class>com.example.demo.DemoApplication</Main-Class>
                        </manifestEntries>
                    </transformer>
                    <transformer
                                 implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                </transformers>
                <artifactSet>
                </artifactSet>
                <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
                </outputFile>
            </configuration>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>${exec-maven-plugin.version}</version>
    <configuration>
        <mainClass>com.example.demo.DemoApplication</mainClass>
    </configuration>
</plugin>

Now run the following command to start the application.

mvn clean compile exec:java

//or
mvn clean package
java -jar target\xxx-fat.jar

In the TestMainVerticle, let’s do some modifications to use Spring IOC container, eg. you can get a Vertx instance from the Spring application context.

@SpringJUnitConfig(classes = DemoApplication.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(VertxExtension.class)
public class TestMainVerticle {
    private final static Logger LOGGER = Logger.getLogger(TestMainVerticle.class.getName());

    @Autowired
    ApplicationContext context;

    Vertx vertx;

    @BeforeAll
    public void setupAll(VertxTestContext testContext) {
        vertx = context.getBean(Vertx.class);
        var factory = context.getBean(VerticleFactory.class);
        vertx.deployVerticle(factory.prefix() + ":" + MainVerticle.class.getName())
            .onSuccess(id -> {
                LOGGER.info("deployed:" + id);
                testContext.completeNow();
            })
            .onFailure(testContext::failNow);
    }

    @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());
                testContext.failNow(e);
            });
    }
}

Get the example codes from my github.