Qualifying by Generic Type Arguments

Since Micronaut 3.0, it is possible to select which bean to inject based on the generic type arguments of the class or interface. Consider the following example:

  1. public interface CylinderProvider {
  2. int getCylinders();
  3. }
  1. interface CylinderProvider {
  2. int getCylinders()
  3. }
  1. interface CylinderProvider {
  2. val cylinders: Int
  3. }

The CylinderProvider interface provides the number of cylinders.

  1. public interface Engine<T extends CylinderProvider> { (1)
  2. default int getCylinders() {
  3. return getCylinderProvider().getCylinders();
  4. }
  5. default String start() {
  6. return "Starting " + getCylinderProvider().getClass().getSimpleName();
  7. }
  8. T getCylinderProvider();
  9. }
  1. interface Engine<T extends CylinderProvider> { (1)
  2. default int getCylinders() { cylinderProvider.cylinders }
  3. default String start() { "Starting ${cylinderProvider.class.simpleName}" }
  4. T getCylinderProvider()
  5. }
  1. interface Engine<T : CylinderProvider> { (1)
  2. val cylinders: Int
  3. get() = cylinderProvider.cylinders
  4. fun start(): String {
  5. return "Starting ${cylinderProvider.javaClass.simpleName}"
  6. }
  7. val cylinderProvider: T
  8. }
1The engine class defines a generic type argument <T> that must be an instance of CylinderProvider

You can define implementations of the Engine interface with different generic type arguments. For example for a V6 engine:

  1. public class V6 implements CylinderProvider {
  2. @Override
  3. public int getCylinders() {
  4. return 7;
  5. }
  6. }
  1. class V6 implements CylinderProvider {
  2. @Override
  3. int getCylinders() { 7 }
  4. }
  1. class V6 : CylinderProvider {
  2. override val cylinders: Int = 7
  3. }

The above defines a V6 class that implements the CylinderProvider interface.

  1. @Singleton
  2. public class V6Engine implements Engine<V6> { (1)
  3. @Override
  4. public V6 getCylinderProvider() {
  5. return new V6();
  6. }
  7. }
  1. @Singleton
  2. class V6Engine implements Engine<V6> { (1)
  3. @Override
  4. V6 getCylinderProvider() { new V6() }
  5. }
  1. @Singleton
  2. class V6Engine : Engine<V6> { (1)
  3. override val cylinderProvider: V6
  4. get() = V6()
  5. }
1The V6Engine implements Engine providing V6 as a generic type parameter

And a V8 engine:

  1. public class V8 implements CylinderProvider {
  2. @Override
  3. public int getCylinders() {
  4. return 8;
  5. }
  6. }
  1. class V8 implements CylinderProvider {
  2. @Override
  3. int getCylinders() { 8 }
  4. }
  1. class V8 : CylinderProvider {
  2. override val cylinders: Int = 8
  3. }

The above defines a V8 class that implements the CylinderProvider interface.

  1. @Singleton
  2. public class V8Engine implements Engine<V8> { (1)
  3. @Override
  4. public V8 getCylinderProvider() {
  5. return new V8();
  6. }
  7. }
  1. @Singleton
  2. class V8Engine implements Engine<V8> { (1)
  3. @Override
  4. V8 getCylinderProvider() { new V8() }
  5. }
  1. @Singleton
  2. class V8Engine : Engine<V8> { (1)
  3. override val cylinderProvider: V8
  4. get() = V8()
  5. }
1The V8Engine implements Engine providing V8 as a generic type parameter

You can then use the generic arguments when defining the injection point and Micronaut will pick the correct bean to inject based on the specific generic type arguments:

  1. @Inject
  2. public Vehicle(Engine<V8> engine) {
  3. this.engine = engine;
  4. }
  1. @Inject
  2. Vehicle(Engine<V8> engine) {
  3. this.engine = engine
  4. }
  1. @Singleton
  2. class Vehicle(val engine: Engine<V8>) {

In the above example the V8Engine bean is injected.