Qualifying By Annotation Members

Since Micronaut 3.0, annotation qualifiers can also use annotation members to resolve the correct bean to inject. For example, consider the following annotation:

  1. import io.micronaut.context.annotation.NonBinding;
  2. import jakarta.inject.Qualifier;
  3. import java.lang.annotation.Retention;
  4. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  5. @Qualifier (1)
  6. @Retention(RUNTIME)
  7. public @interface Cylinders {
  8. int value();
  9. @NonBinding (2)
  10. String description() default "";
  11. }
  1. import io.micronaut.context.annotation.NonBinding
  2. import jakarta.inject.Qualifier
  3. import java.lang.annotation.Retention
  4. import static java.lang.annotation.RetentionPolicy.RUNTIME
  5. @Qualifier (1)
  6. @Retention(RUNTIME)
  7. @interface Cylinders {
  8. int value();
  9. @NonBinding (2)
  10. String description() default "";
  11. }
  1. import io.micronaut.context.annotation.NonBinding
  2. import jakarta.inject.Qualifier
  3. import kotlin.annotation.Retention
  4. @Qualifier (1)
  5. @Retention(AnnotationRetention.RUNTIME)
  6. annotation class Cylinders(
  7. val value: Int,
  8. @get:NonBinding (2)
  9. val description: String = ""
  10. )
1The @Cylinders annotation is meta-annotated with @Qualifier
2The annotation has two members. The @NonBinding annotation is used to exclude the description member from being considered during dependency resolution.

You can then use the @Cylinders annotation on any bean and the members that are not annotated with @NonBinding are considered during dependency resolution:

  1. @Singleton
  2. @Cylinders(value = 6, description = "6-cylinder V6 engine") (1)
  3. public class V6Engine implements Engine { (2)
  4. @Override
  5. public int getCylinders() {
  6. return 6;
  7. }
  8. @Override
  9. public String start() {
  10. return "Starting V6";
  11. }
  12. }
  1. @Singleton
  2. @Cylinders(value = 6, description = "6-cylinder V6 engine") (1)
  3. class V6Engine implements Engine { (2)
  4. @Override
  5. int getCylinders() {
  6. return 6
  7. }
  8. @Override
  9. String start() {
  10. return "Starting V6"
  11. }
  12. }
  1. @Singleton
  2. @Cylinders(value = 6, description = "6-cylinder V6 engine") (1)
  3. class V6Engine : Engine { (2)
  4. (2)
  5. override val cylinders: Int
  6. get() = 6
  7. override fun start(): String {
  8. return "Starting V6"
  9. }
  10. }
1Here the value member is set to 6 for the V6Engine type
2The class implements an Engine interface
  1. @Singleton
  2. @Cylinders(value = 8, description = "8-cylinder V8 engine") (1)
  3. public class V8Engine implements Engine { (2)
  4. @Override
  5. public int getCylinders() {
  6. return 8;
  7. }
  8. @Override
  9. public String start() {
  10. return "Starting V8";
  11. }
  12. }
  1. @Singleton
  2. @Cylinders(value = 8, description = "8-cylinder V8 engine") (1)
  3. class V8Engine implements Engine { (2)
  4. @Override
  5. int getCylinders() {
  6. return 8
  7. }
  8. @Override
  9. String start() {
  10. return "Starting V8"
  11. }
  12. }
  1. @Singleton
  2. @Cylinders(value = 8, description = "8-cylinder V8 engine") (1)
  3. class V8Engine : Engine { (2)
  4. override val cylinders: Int
  5. get() = 8
  6. override fun start(): String {
  7. return "Starting V8"
  8. }
  9. }
1Here the value member is set to 8 for the V8Engine type
2The class implements an Engine interface

You can then use the @Cylinders qualifier on any injection point to select the correct bean to inject. For example:

  1. @Inject Vehicle(@Cylinders(8) Engine engine) {
  2. this.engine = engine;
  3. }
  1. @Inject Vehicle(@Cylinders(8) Engine engine) {
  2. this.engine = engine
  3. }
  1. @Singleton
  2. class Vehicle(@param:Cylinders(8) val engine: Engine) {
  3. fun start(): String {
  4. return engine.start()
  5. }
  6. }