阅读(1503) (0)

Micronaut Bean 限定符

2023-02-23 10:51:50 更新

如果要注入的给定接口有多个可能的实现,则需要使用限定符。

Micronaut 再次利用 JSR-330 以及限定符和命名注释来支持此用例。

按名称限定

要按名称限定,请使用 Named 注释。例如,考虑以下类:

 Java Groovy  Kotlin 
public interface Engine { // (1)
    int getCylinders();
    String start();
}

@Singleton
public class V6Engine implements Engine {  // (2)
    @Override
    public String start() {
        return "Starting V6";
    }

    @Override
    public int getCylinders() {
        return 6;
    }
}

@Singleton
public class V8Engine implements Engine {  // (3)
    @Override
    public String start() {
        return "Starting V8";
    }

    @Override
    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

    @Override
    String start() {
        "Starting V6"
    }
}

@Singleton
class V8Engine implements Engine { // (3)
    int cylinders = 8

    @Override
    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. Engine 接口定义了公共契约

  2. V6Engine 类是第一个实现

  3. V8Engine 类是第二个实现

  4. javax.inject.Named注解表示需要V8Engine实现

  5. 调用 start 方法打印:“Starting V8”

Micronaut 能够在前面的示例中注入 V8Engine,因为:

@Named 限定符值 (v8) + 被注入的类型简单名称 (Engine) ==(不区分大小写)== Engine (V8Engine) 类型的 bean 的简单名称

您还可以在 bean 的类级别声明 @Named 以显式定义 bean 的名称。

通过注释限定

除了能够按名称进行限定外,您还可以使用 Qualifier 注释构建自己的限定符。例如,考虑以下注解:

 Java Groovy  Kotlin 
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
public @interface V8 {
}
import jakarta.inject.Qualifier
import java.lang.annotation.Retention

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Qualifier
@Retention(RUNTIME)
@interface V8 {
}
import jakarta.inject.Qualifier
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.RUNTIME

@Qualifier
@Retention(RUNTIME)
annotation class V8

上面的注释本身使用@Qualifier 注释进行注释,以将其指定为限定符。然后,您可以在代码中的任何注入点使用注释。例如:

 Java Groovy  Kotlin 
@Inject Vehicle(@V8 Engine engine) {
    this.engine = engine;
}
@Inject Vehicle(@V8 Engine engine) {
    this.engine = engine
}
@Inject constructor(@V8 val engine: Engine) {

注释成员限定

从 Micronaut 3.0 开始,注释限定符还可以使用注释成员来解析要注入的正确 bean。例如,考虑以下注解:

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.NonBinding;
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier // (1)
@Retention(RUNTIME)
public @interface Cylinders {
    int value();

    @NonBinding // (2)
    String description() default "";
}
import io.micronaut.context.annotation.NonBinding
import jakarta.inject.Qualifier
import java.lang.annotation.Retention

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Qualifier // (1)
@Retention(RUNTIME)
@interface Cylinders {
    int value();

    @NonBinding // (2)
    String description() default "";
}
import io.micronaut.context.annotation.NonBinding
import jakarta.inject.Qualifier
import kotlin.annotation.Retention

@Qualifier // (1)
@Retention(AnnotationRetention.RUNTIME)
annotation class Cylinders(
    val value: Int,
    @get:NonBinding // (2)
    val description: String = ""
)
  1. @Cylinders 注释使用@Qualifier 进行元注释

  2. 注释有两个成员。 @NonBinding 注释用于在依赖项解析期间将 description 成员排除在考虑范围之外。

然后,您可以在任何 bean 上使用 @Cylinders 注释,并且在依赖项解析期间会考虑未使用 @NonBinding 注释的成员:

 Java Groovy  Kotlin 
@Singleton
@Cylinders(value = 6, description = "6-cylinder V6 engine")  // (1)
public class V6Engine implements Engine { // (2)

    @Override
    public int getCylinders() {
        return 6;
    }

    @Override
    public String start() {
        return "Starting V6";
    }
}
@Singleton
@Cylinders(value = 6, description = "6-cylinder V6 engine")  // (1)
class V6Engine implements Engine { // (2)

    @Override
    int getCylinders() {
        return 6
    }

    @Override
    String start() {
        return "Starting V6"
    }
}
@Singleton
@Cylinders(value = 6, description = "6-cylinder V6 engine") // (1)
class V6Engine : Engine { // (2)
    // (2)
    override val cylinders: Int
        get() = 6

    override fun start(): String {
        return "Starting V6"
    }
}
  1. 这里的值成员为 V6Engine 类型设置为 6

  2. 该类实现了一个 Engine 接口

 Java Groovy  Kotlin 
@Singleton
@Cylinders(value = 8, description = "8-cylinder V8 engine") // (1)
public class V8Engine implements Engine { // (2)
    @Override
    public int getCylinders() {
        return 8;
    }

    @Override
    public String start() {
        return "Starting V8";
    }
}
@Singleton
@Cylinders(value = 8, description = "8-cylinder V8 engine") // (1)
class V8Engine implements Engine { // (2)
    @Override
    int getCylinders() {
        return 8
    }

    @Override
    String start() {
        return "Starting V8"
    }
}
@Singleton
@Cylinders(value = 8, description = "8-cylinder V8 engine") // (1)
class V8Engine : Engine { // (2)
    override val cylinders: Int
        get() = 8

    override fun start(): String {
        return "Starting V8"
    }
}
  1. 这里的值成员设置为 8 对于 V8Engine 类型

  2. 该类实现了一个 Engine 接口

然后,您可以在任何注入点上使用 @Cylinders 限定符来选择要注入的正确 bean。例如:

 Java Groovy  Kotlin 
@Inject Vehicle(@Cylinders(8) Engine engine) {
    this.engine = engine;
}
@Inject Vehicle(@Cylinders(8) Engine engine) {
    this.engine = engine
}
@Singleton
class Vehicle(@param:Cylinders(8) val engine: Engine) {
    fun start(): String {
        return engine.start()
    }
}

通过泛型类型参数进行限定

从 Micronaut 3.0 开始,可以根据类或接口的泛型类型参数来选择注入哪个 bean。考虑以下示例:

 Java Groovy  Kotlin 
public interface CylinderProvider {
    int getCylinders();
}
interface CylinderProvider {
    int getCylinders()
}
interface CylinderProvider {
    val cylinders: Int
}

CylinderProvider接口提供了 cylinders 的数量。

 Java Groovy  Kotlin 
public interface Engine<T extends CylinderProvider> { // (1)
    default int getCylinders() {
        return getCylinderProvider().getCylinders();
    }

    default String start() {
        return "Starting " + getCylinderProvider().getClass().getSimpleName();
    }

    T getCylinderProvider();
}
interface Engine<T extends CylinderProvider> { // (1)
    default int getCylinders() { cylinderProvider.cylinders }

    default String start() { "Starting ${cylinderProvider.class.simpleName}" }

    T getCylinderProvider()
}
interface Engine<T : CylinderProvider> { // (1)
    val cylinders: Int
        get() = cylinderProvider.cylinders

    fun start(): String {
        return "Starting ${cylinderProvider.javaClass.simpleName}"
    }

    val cylinderProvider: T
}
  1. 引擎类定义了一个泛型类型参数 <T>,它必须是 CylinderProvider 的一个实例

您可以使用不同的通用类型参数定义 Engine 接口的实现。例如对于 V6 引擎:

 Java Groovy  Kotlin 
public class V6 implements CylinderProvider {
    @Override
    public int getCylinders() {
        return 6;
    }
}
class V6 implements CylinderProvider {
    @Override
    int getCylinders() { 6 }
}
class V6 : CylinderProvider {
    override val cylinders: Int = 6
}

上面定义了一个实现了 CylinderProvider 接口的 V6 类。

 Java Groovy  Kotlin 
@Singleton
public class V6Engine implements Engine<V6> {  // (1)
    @Override
    public V6 getCylinderProvider() {
        return new V6();
    }
}
@Singleton
class V6Engine implements Engine<V6> {  // (1)
    @Override
    V6 getCylinderProvider() { new V6() }
}
@Singleton
class V6Engine : Engine<V6> { // (1)
    override val cylinderProvider: V6
        get() = V6()
}
  1.  V6Engine 实现 Engine 提供 V6 作为通用类型参数

和 V8 引擎:

 Java Groovy  Kotlin 
public class V8 implements CylinderProvider {
    @Override
    public int getCylinders() {
        return 8;
    }
}
class V8 implements CylinderProvider {
    @Override
    int getCylinders() { 8 }
}
class V8 : CylinderProvider {
    override val cylinders: Int = 8
}

上面定义了一个实现了 CylinderProvider 接口的 V8 类。

 Java Groovy  Kotlin 
@Singleton
public class V8Engine implements Engine<V8> {  // (1)
    @Override
    public V8 getCylinderProvider() {
        return new V8();
    }
}
@Singleton
class V8Engine implements Engine<V8> {  // (1)
    @Override
    V8 getCylinderProvider() { new V8() }
}
@Singleton
class V8Engine : Engine<V8> { // (1)
    override val cylinderProvider: V8
        get() = V8()
}
  1. V8Engine 实现 Engine 提供 V8 作为通用类型参数

然后,您可以在定义注入点时使用泛型参数,Micronaut 将根据特定的泛型类型参数选择正确的 bean 进行注入:

 Java Groovy  Kotlin 
@Inject
public Vehicle(Engine<V8> engine) {
    this.engine = engine;
}
@Inject
Vehicle(Engine<V8> engine) {
    this.engine = engine
}
@Singleton
class Vehicle(val engine: Engine<V8>) {

在上面的示例中,注入了 V8Engine bean。

初级和次级 Bean

Primary 是一个限定符,表示在多个接口实现的情况下,一个 bean 是要选择的主要 bean。

考虑以下示例:

 Java Groovy  Kotlin 
public interface ColorPicker {
    String color();
}
interface ColorPicker {
    String color()
}
interface ColorPicker {
    fun color(): String
}

ColorPicker 由这些类实现:

初级 Bean

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;

@Primary
@Singleton
class Green implements ColorPicker {

    @Override
    public String color() {
        return "green";
    }
}
import io.micronaut.context.annotation.Primary
import jakarta.inject.Singleton

@Primary
@Singleton
class Green implements ColorPicker {

    @Override
    String color() {
        return "green"
    }
}
import io.micronaut.context.annotation.Primary
import jakarta.inject.Singleton

@Primary
@Singleton
class Green: ColorPicker {
    override fun color(): String {
        return "green"
    }
}

Green bean 类实现了 ColorPicker 并使用@Primary 注释。

另一个相同类型的 Bean

 Java Groovy  Kotlin 
import jakarta.inject.Singleton;

@Singleton
public class Blue implements ColorPicker {

    @Override
    public String color() {
        return "blue";
    }
}
import jakarta.inject.Singleton

@Singleton
class Blue implements ColorPicker {

    @Override
    String color() {
        return "blue"
    }
}
import jakarta.inject.Singleton

@Singleton
class Blue: ColorPicker {
    override fun color(): String {
        return "blue"
    }
}

Blue bean 类还实现了 ColorPicker,因此在注入 ColorPicker 接口时您有两个可能的候选对象。由于绿色是主要的,所以它总是受到青睐。

 Java Groovy  Kotlin 
@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. 尽管有两个 ColorPicker bean,但由于 @Primary 注释,Green 被注入。

如果存在多个可能的候选者并且没有定义 @Primary,则抛出 NonUniqueBeanException。

除了 @Primary 之外,还有一个 Secondary 注释会导致相反的效果并允许降低 bean 的优先级。

注入任何 Bean

如果您不特别注意注入哪个 bean,那么您可以使用 @Any 限定符来注入第一个可用的 bean,例如:

注入任何实例

 Java Groovy  Kotlin 
@Inject @Any
Engine engine;
@Inject @Any
Engine engine
@Inject
@field:Any
lateinit var engine: Engine

@Any 限定符通常与 BeanProvider 接口结合使用以允许更多动态用例。例如,如果存在 bean,则以下 Vehicle 实现将启动引擎:

将 BeanProvider 与 Any 一起使用

 Java Groovy  Kotlin 
import io.micronaut.context.BeanProvider;
import io.micronaut.context.annotation.Any;
import jakarta.inject.Singleton;

@Singleton
public class Vehicle {
    final BeanProvider<Engine> engineProvider;

    public Vehicle(@Any BeanProvider<Engine> engineProvider) { // (1)
        this.engineProvider = engineProvider;
    }
    void start() {
        engineProvider.ifPresent(Engine::start); // (2)
    }
}
import io.micronaut.context.BeanProvider
import io.micronaut.context.annotation.Any
import jakarta.inject.Singleton

@Singleton
class Vehicle {
    final BeanProvider<Engine> engineProvider

    Vehicle(@Any BeanProvider<Engine> engineProvider) { // (1)
        this.engineProvider = engineProvider
    }
    void start() {
        engineProvider.ifPresent(Engine::start) // (2)
    }
}
import io.micronaut.context.BeanProvider
import io.micronaut.context.annotation.Any
import jakarta.inject.Singleton

@Singleton
class Vehicle(@param:Any val engineProvider: BeanProvider<Engine>) { // (1)
    fun start() {
        engineProvider.ifPresent { it.start() } // (2)
    }
    fun startAll() {
        if (engineProvider.isPresent) { // (1)
            engineProvider.forEach { it.start() } // (2)
        }
}
  1. 使用@Any 注入 BeanProvider

  2. 如果使用 ifPresent 方法存在底层 bean,则调用 start 方法

如果有多个 bean,您还可以调整行为。以下示例启动 Vehicle 中安装的所有引擎(如果存在):

将 BeanProvider 与 Any 一起使用

 Java Groovy  Kotlin 
void startAll() {
    if (engineProvider.isPresent()) { // (1)
        engineProvider.stream().forEach(Engine::start); // (2)
    }
}
void startAll() {
    if (engineProvider.isPresent()) { // (1)
        engineProvider.each {it.start() } // (2)
    }
}
fun startAll() {
    if (engineProvider.isPresent) { // (1)
        engineProvider.forEach { it.start() } // (2)
    }
  1. 检查是否有 bean 存在

  2. 如果是这样,通过 stream().forEach(..) 方法遍历每一个,启动引擎