Spring Dependency Injection

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 step explains how your Quarkus application can leverage the well known Dependency Injection annotations included in the Spring Framework.

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

Creating the Functional Interface

First you'll create a StringFunction interface that some of your beans will implement and which will be injected into another bean later on. This functional interface provides target types for lambda expressions and method references you'll define.  

Note: Functional Interfaces are part of the base Java platform, and are not Spring-specific.
Create a new file in the project at:
src/main/java/org/acme/quickstart/StringFunction.java

Add this code for the interface:

package org.acme.quickstart;

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 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 a parameter.

Create a new file at:
src/main/java/org/acme/quickstart/AppConfiguration.java

Add this code:

package org.acme.quickstart;

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

Creating Functions

The next step is to 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 new file at:
src/main/java/org/acme/quickstart/NoOpSingleStringFunction.java

Add this code:

package org.acme.quickstart;

import org.springframework.stereotype.Component;

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

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

Adding Injectable Configuration

Quarkus also provides support for injecting configuration values using Spring’s @Value annotation. Configuration parameters are added to the file:  

src/main/resources/application.properties
Open that file and add the following value:
taste.message = tastes great
You'll also need to add a new Spring Bean to use this configuration value. Create a new file at:
src/main/java/org/acme/quickstart/MessageProducer.java

Add this code:

package org.acme.quickstart;

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

@Service
public class MessageProducer {

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

    public String getTaste() {
        return message;
    }
}

Bringing Everything Together

The final bean you'll create ties together all of the previous beans.  

Create a new file for this final bean at:

src/main/java/org/acme/quickstart/TasterBean.java

Add this code:

package org.acme.quickstart;

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 TasterBean {

    private final MessageProducer messageProducer;

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

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

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

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

    public String taste(String fruitName) {
        final String initialValue = fruitName + ": " + messageProducer.getTaste() + " " + 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 taste.suffix in application.properties.

This new TasterBean has a method taste() that will report how each fruit tastes. It also uses our functions noopStringFunction and capitalizerStringFunction that we've injected via @Autowired to both do nothing and also transform the result INTO ALL CAPS.

With our data model, repository, and accessor beans in place, let's move to the final step where we'll expose our fruits to the outside world via Spring Web annotations.

Daniel Oh
Daniel Oh
Senior Principal Developer Advocate
Daniel Oh is a Senior Principal Developer Advocate at Red Hat. He works to evangelize building cloud-native microservices and serverless functions with cloud-native runtimes to developers. He also continues to contribute to various open-source cloud projects and ecosystems as a Cloud Native Computing Foundation (CNCF) ambassador for accelerating DevOps adoption in enterprises. Daniel also speaks at technical seminars, workshops, and meetups to elaborate on new emerging technologies for enterprise developers, SREs, platform engineers, and DevOps teams.