Quarkus - Using our Spring Dependency Injection compatibility layer

While you are encouraged to use CDI annotations for injection, Quarkus provides a compatibility layer for Spring dependency injection in the form of the spring-di extension.

This guide explains how your Quarkus application can leverage the well known Dependency Injection annotations included in the Spring Framework.

Prerequisites

To complete this guide, you need:

  • less than 15 minutes

  • an IDE

  • JDK 1.8+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.5.3+

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

The solution is located in the using-spring-di directory.

Creating the Maven project

First, we need a new project. Create a new project with the following command:

mvn io.quarkus:quarkus-maven-plugin:0.19.1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=using-spring-di \
    -DclassName="org.acme.spring.di.GreeterResource" \
    -Dpath="/greeting" \
    -Dextensions="spring-di"

This command generates a Maven project with a REST endpoint and imports the spring-di extension.

Examine the pom.xml file

In the pom.xml file, we see that the quarkus-spring-di dependency has been added. We also need to add a dependency on the spring-context artifact, which we can do by adding:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

where spring.version can be any version higher than 3.2.0.RELEASE.

Add beans using Spring annotations

Let’s proceed to create some beans using various Spring annotations.

First we will create a StringFunction interface that some of our beans will implement and which will be injected into another bean later on. Create a src/main/java/org/acme/spring/di/StringFunction.java file and set the following content:

package org.acme.spring.di;

import java.util.function.Function;

public interface StringFunction extends Function<String, String> {

}

With the interface in place, we will add an AppConfiguration class which will use the Spring’s Java Config style for defining a bean. It will be used to create a StringFunction bean that will capitalize the text passed as parameter. Create a src/main/java/org/acme/spring/di/AppConfiguration.java file with the following content:

package org.acme.spring.di;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfiguration {

    @Bean(name = "capitalizeFunction")
    public StringFunction capitalizer() {
        return String::toUpperCase;
    }
}

Now we define another bean that will implement StringFunction using Spring’s stereotype annotation style. This bean will effectively be a no-op bean that simply returns the input as is. Create a src/main/java/org/acme/spring/di/NoOpSingleStringFunction.java file and set the following content:

package org.acme.spring.di;

import org.springframework.stereotype.Component;

@Component("noopFunction")
public class NoOpSingleStringFunction implements StringFunction {

    @Override
    public String apply(String s) {
        return s;
    }
}

Quarkus also provides support for injecting configuration values using Spring’s @Value annotation. To see that in action, first edit the src/main/resources/application.properties with the following content:

# Your configuration properties
greeting.message = hello

Next create a new Spring bean in src/main/java/org/acme/spring/di/MessageProducer.java with the following content:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class MessageProducer {

    @Value("${greeting.message}")
    String message;

    public String getPrefix() {
        return message;
    }
}

The final bean we will create ties together all the previous beans. Create a src/main/java/org/acme/spring/di/GreeterBean.java file and copy the following content:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GreeterBean {

    private final MessageProducer messageProducer;

    @Autowired
    @Qualifier("noopFunction")
    StringFunction noopStringFunction;

    @Autowired
    @Qualifier("capitalizeFunction")
    StringFunction capitalizerStringFunction;

    @Value("${greeting.suffix:!}")
    String suffix;

    public GreeterBean(MessageProducer messageProducer) {
        this.messageProducer = messageProducer;
    }

    public String greet(String name) {
        final String initialValue = messageProducer.getPrefix() + " " + name + suffix;
        return noopStringFunction.andThen(capitalizerStringFunction).apply(initialValue);
    }
}

In the code above, we see that both field injection and constructor injection are being used (note that constructor injection does not need the @Autowired annotation since there is a single constructor). Furthermore, the @Value annotation on suffix has also a default value defined, which in this case will be used since we have not defined greeting.suffix in application.properties.

Update the JAX-RS resource

Open the src/main/java/org/acme/spring/di/GreeterResource.java file and update it with the following content:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Autowired;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/greeting")
public class GreeterResource {

    @Autowired
    GreeterBean greeterBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return greeterBean.greet("world");
    }
}

Update the test

We also need to update the functional test to reflect the changes made to the endpoint. Edit the src/test/java/org/acme/spring/di/GreetingResourceTest.java file and change the content of the testHelloEndpoint method to:

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
            .when().get("/greeting")
            .then()
                .statusCode(200)
                .body(is("HELLO WORLD!"));
    }

}

Package and run the application

Run the application with: ./mvnw compile quarkus:dev. Open your browser to http://localhost:8080/greeting.

The result should be: HELLO WORLD!.