3.5 Bean Qualifiers
If you have multiple possible implementations for a given interface that you want to inject, you need to use a qualifier.
Once again Micronaut leverages JSR-330 and the Qualifier and Named annotations to support this use case.
Qualifying By Name
To qualify by name you can use the Named annotation. For example, consider the following classes:
public interface Engine { (1)
int getCylinders();
String start();
}
@Singleton
public class V6Engine implements Engine { (2)
public String start() {
return "Starting V6";
}
public int getCylinders() {
return 6;
}
}
@Singleton
public class V8Engine implements Engine {
public String start() {
return "Starting V8";
}
public int getCylinders() {
return 8;
}
}
@Singleton
public class Vehicle {
private final Engine engine;
@Inject
public Vehicle(@Named("v8") Engine engine) {(4)
this.engine = engine;
}
public String start() {
return engine.start();(5)
}
}
interface Engine { (1)
int getCylinders()
String start()
}
@Singleton
class V6Engine implements Engine { (2)
int cylinders = 6
String start() {
"Starting V6"
}
}
@Singleton
class V8Engine implements Engine { (3)
int cylinders = 8
String start() {
"Starting V8"
}
}
@Singleton
class Vehicle {
final Engine engine
@Inject Vehicle(@Named('v8') Engine engine) { (4)
this.engine = engine
}
String start() {
engine.start() (5)
}
}
interface Engine { (1)
val cylinders: Int
fun start(): String
}
@Singleton
class V6Engine : Engine { (2)
override var cylinders: Int = 6
override fun start(): String {
return "Starting V6"
}
}
@Singleton
class V8Engine : Engine {
override var cylinders: Int = 8
override fun start(): String {
return "Starting V8"
}
}
@Singleton
class Vehicle @Inject
constructor(@param:Named("v8") private val engine: Engine)(4)
{
fun start(): String {
return engine.start()(5)
}
}
1 | The Engine interface defines the common contract |
2 | The V6Engine class is the first implementation |
3 | The V8Engine class is the second implementation |
4 | The Named annotation is used to indicate the V8Engine implementation is required |
5 | Calling the start method prints: “Starting V8” |
You can also declare @Named at the class level of a bean to explicitly define the name of the bean.
Qualifying By Annotation
In addition to being able to qualify by name, you can build your own qualifiers using the Qualifier annotation. For example, consider the following annotation:
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
public @interface V8 {
}
import javax.inject.Qualifier
import java.lang.annotation.Retention
import static java.lang.annotation.RetentionPolicy.RUNTIME
@Qualifier
@Retention(RUNTIME)
@interface V8 {
}
import javax.inject.Qualifier
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.RUNTIME
@Qualifier
@Retention(RUNTIME)
annotation class V8
The above annotation is itself annotated with the @Qualifier
annotation to designate it as a qualifier. You can then use the annotation at any injection point in your code. For example:
@Inject Vehicle(@V8 Engine engine) {
this.engine = engine;
}
@Inject Vehicle(@V8 Engine engine) {
this.engine = engine
}
@Inject constructor(@V8 val engine: Engine) {
Primary and Secondary Beans
Primary is a qualifier that indicates that a bean is the primary bean that should be selected in the case of multiple possible interface implementations.
Consider the following example:
public interface ColorPicker {
String color();
}
interface ColorPicker {
String color()
}
interface ColorPicker {
fun color(): String
}
Given a common interface called ColorPicker
that is implemented by multiple classes.
The Primary Bean
import io.micronaut.context.annotation.Primary;
import javax.inject.Singleton;
@Primary
@Singleton
class Green implements ColorPicker {
@Override
public String color() {
return "green";
}
}
The Primary Bean
import io.micronaut.context.annotation.Primary
import javax.inject.Singleton
@Primary
@Singleton
class Green implements ColorPicker {
@Override
String color() {
return "green"
}
}
The Primary Bean
import io.micronaut.context.annotation.Primary
import javax.inject.Singleton
@Primary
@Singleton
class Green: ColorPicker {
override fun color(): String {
return "green"
}
}
The Green
bean is a ColorPicker
, but is annotated with @Primary
.
Another Bean of the Same Type
import javax.inject.Singleton;
@Singleton
public class Blue implements ColorPicker {
@Override
public String color() {
return "blue";
}
}
Another Bean of the Same Type
import javax.inject.Singleton
@Singleton
class Blue implements ColorPicker {
@Override
String color() {
return "blue"
}
}
Another Bean of the Same Type
import javax.inject.Singleton
@Singleton
class Blue: ColorPicker {
override fun color(): String {
return "blue"
}
}
The Blue
bean is also a ColorPicker
and hence you have two possible candidates when injecting the ColorPicker
interface. Since Green
is the primary it will always be favoured.
@Controller("/testPrimary")
public class TestController {
protected final ColorPicker colorPicker;
public TestController(ColorPicker colorPicker) { (1)
this.colorPicker = colorPicker;
}
@Get
public String index() {
return colorPicker.color();
}
}
@Controller("/test")
class TestController {
protected final ColorPicker colorPicker
TestController(ColorPicker colorPicker) { (1)
this.colorPicker = colorPicker
}
@Get
String index() {
colorPicker.color()
}
}
@Controller("/test")
class TestController(val colorPicker: ColorPicker) { (1)
@Get
fun index(): String {
return colorPicker.color()
}
}
1 | Although there are two ColorPicker beans, Green gets injected due to the @Primary annotation. |
If multiple possible candidates are present and no @Primary
is defined then a NonUniqueBeanException will be thrown.
In addition to @Primary
, there is also a Secondary annotation which causes the opposite effect and allows de-prioritizing a bean.