Micronaut Bean Factories

2023-02-27 15:26 更新

在許多情況下,您可能希望將不屬于您的代碼庫(kù)的類作為 bean 提供,例如第三方庫(kù)提供的類。在這種情況下,您不能注釋已編譯的類。相反,實(shí)現(xiàn)一個(gè)@Factory。

工廠是一個(gè)用 Factory 注釋注釋的類,它提供一個(gè)或多個(gè)用 bean 范圍注釋注釋的方法。您使用哪個(gè)注釋取決于您希望 bean 處于什么范圍。

工廠具有默認(rèn)范圍單例,將隨上下文一起銷毀。如果你想在它產(chǎn)生一個(gè) bean 之后處理工廠,請(qǐng)使用 @Prototype 范圍。

用 bean 范圍注釋注釋的方法的返回類型是 bean 類型。這最好用一個(gè)例子來(lái)說(shuō)明:

 Java Groovy  Kotlin 
@Singleton
class CrankShaft {
}

class V8Engine implements Engine {
    private final int cylinders = 8;
    private final CrankShaft crankShaft;

    public V8Engine(CrankShaft crankShaft) {
        this.crankShaft = crankShaft;
    }

    @Override
    public String start() {
        return "Starting V8";
    }
}

@Factory
class EngineFactory {

    @Singleton
    Engine v8Engine(CrankShaft crankShaft) {
        return new V8Engine(crankShaft);
    }
}
@Singleton
class CrankShaft {
}

class V8Engine implements Engine {
    final int cylinders = 8
    final CrankShaft crankShaft

    V8Engine(CrankShaft crankShaft) {
        this.crankShaft = crankShaft
    }

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

@Factory
class EngineFactory {

    @Singleton
    Engine v8Engine(CrankShaft crankShaft) {
        new V8Engine(crankShaft)
    }
}
@Singleton
internal class CrankShaft

internal class V8Engine(private val crankShaft: CrankShaft) : Engine {
    private val cylinders = 8

    override fun start(): String {
        return "Starting V8"
    }
}

@Factory
internal class EngineFactory {

    @Singleton
    fun v8Engine(crankShaft: CrankShaft): Engine {
        return V8Engine(crankShaft)
    }
}

在這種情況下,V8Engine 由 EngineFactory 類的 v8Engine 方法創(chuàng)建。請(qǐng)注意,您可以將參數(shù)注入方法,它們將被解析為 beans。生成的 V8Engine bean 將是一個(gè)單例。

一個(gè)工廠可以有多個(gè)用 bean 范圍注解注解的方法,每個(gè)方法返回一個(gè)不同的 bean 類型。

如果采用這種方法,則不應(yīng)在類內(nèi)部調(diào)用其他 bean 方法。相反,通過(guò)參數(shù)注入類型。

要允許生成的 bean 參與應(yīng)用程序上下文關(guān)閉過(guò)程,請(qǐng)使用 @Bean 注釋方法并將 preDestroy 參數(shù)設(shè)置為要調(diào)用以關(guān)閉 bean 的方法的名稱。

Beans from Fields

使用 Micronaut 3.0 或更高版本,還可以通過(guò)在字段上聲明 @Bean 注釋來(lái)從字段中生成 bean。

雖然通常不鼓勵(lì)這種方法而支持工廠方法,它提供了更多的靈活性,但它確實(shí)簡(jiǎn)化了測(cè)試代碼。例如,使用 bean 字段,您可以輕松地在測(cè)試代碼中生成模擬:

 Java  Groovy  Kotlin
import io.micronaut.context.annotation.*;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
public class VehicleMockSpec {
    @Requires(beans = VehicleMockSpec.class)
    @Bean @Replaces(Engine.class)
    Engine mockEngine = () -> "Mock Started"; // (1)

    @Inject Vehicle vehicle; // (2)

    @Test
    void testStartEngine() {
        final String result = vehicle.start();
        assertEquals("Mock Started", result); // (3)
    }
}
import io.micronaut.context.annotation.*
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Specification
import jakarta.inject.Inject

@MicronautTest
class VehicleMockSpec extends Specification {
    @Requires(beans=VehicleMockSpec.class)
    @Bean @Replaces(Engine.class)
    Engine mockEngine = {-> "Mock Started" } as Engine  // (1)

    @Inject Vehicle vehicle // (2)

    void "test start engine"() {
        given:
        final String result = vehicle.start()

        expect:
        result == "Mock Started" // (3)
    }
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Replaces
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import jakarta.inject.Inject

@MicronautTest
class VehicleMockSpec {
    @get:Bean
    @get:Replaces(Engine::class)
    val mockEngine: Engine = object : Engine { // (1)
        override fun start(): String {
            return "Mock Started"
        }
    }

    @Inject
    lateinit var vehicle : Vehicle // (2)

    @Test
    fun testStartEngine() {
        val result = vehicle.start()
        Assertions.assertEquals("Mock Started", result) // (3)
    }
}
  1. bean 是從替換現(xiàn)有引擎的字段定義的。

  2. 載體被注入。

  3. 該代碼斷言已調(diào)用模擬實(shí)現(xiàn)。

請(qǐng)注意,非原始類型僅支持公共或包保護(hù)字段。如果該字段是靜態(tài)的、私有的或受保護(hù)的,則會(huì)發(fā)生編譯錯(cuò)誤。

如果 bean 方法/字段包含范圍或限定符,則類型中的任何范圍或限定符都將被省略。

來(lái)自工廠實(shí)例的限定符不會(huì)繼承到 bean。

原始 Bean 和數(shù)組

從 Micronaut 3.1 開(kāi)始,可以從工廠定義和注入原始類型和數(shù)組類型。

例如:

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Named;

@Factory
class CylinderFactory {
    @Bean
    @Named("V8") // (1)
    final int v8 = 8;

    @Bean
    @Named("V6") // (1)
    final int v6 = 6;
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import jakarta.inject.Named

@Factory
class CylinderFactory {
    @Bean
    @Named("V8") // (1)
    final int v8 = 8

    @Bean
    @Named("V6") // (1)
    final int v6 = 6
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import jakarta.inject.Named

@Factory
class CylinderFactory {
    @get:Bean
    @get:Named("V8") // (1)
    val v8 = 8

    @get:Bean
    @get:Named("V6") // (1)
    val v6 = 6
}
  1. 兩個(gè)原始整數(shù) bean 定義為不同的名稱

可以像任何其他 bean 一樣注入原始 bean:

 Java Groovy  Kotlin 
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Singleton
public class V8Engine {
    private final int cylinders;

    public V8Engine(@Named("V8") int cylinders) { // (1)
        this.cylinders = cylinders;
    }

    public int getCylinders() {
        return cylinders;
    }
}
import jakarta.inject.Named
import jakarta.inject.Singleton

@Singleton
class V8Engine {
    private final int cylinders

    V8Engine(@Named("V8") int cylinders) { // (1)
        this.cylinders = cylinders
    }

    int getCylinders() {
        return cylinders
    }
}
import jakarta.inject.Named
import jakarta.inject.Singleton

@Singleton
class V8Engine(
    @param:Named("V8") val cylinders: Int // (1)
)

請(qǐng)注意,原始 bean 和原始數(shù)組 bean 具有以下限制:

  • AOP 建議不能應(yīng)用于基元或包裝器類型

  • 由于上述自定義范圍不支持代理

  • @Bean(preDestroy=..) 成員不受支持

以編程方式禁用 Bean

工廠方法可以拋出 DisabledBeanException 以有條件地禁用 beans。使用@Requires 應(yīng)該始終是有條件地創(chuàng)建 bean 的首選方法;僅當(dāng)無(wú)法使用 @Requires 時(shí)才應(yīng)在工廠方法中拋出異常。

例如:

 Java Groovy  Kotlin 
public interface Engine {
    Integer getCylinders();
}

@EachProperty("engines")
public class EngineConfiguration implements Toggleable {

    private boolean enabled = true;
    private Integer cylinders;

    @NotNull
    public Integer getCylinders() {
        return cylinders;
    }

    public void setCylinders(Integer cylinders) {
        this.cylinders = cylinders;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

}

@Factory
public class EngineFactory {

    @EachBean(EngineConfiguration.class)
    public Engine buildEngine(EngineConfiguration engineConfiguration) {
        if (engineConfiguration.isEnabled()) {
            return engineConfiguration::getCylinders;
        } else {
            throw new DisabledBeanException("Engine configuration disabled");
        }
    }
}
interface Engine {
    Integer getCylinders()
}

@EachProperty("engines")
class EngineConfiguration implements Toggleable {
    boolean enabled = true
    @NotNull
    Integer cylinders
}

@Factory
class EngineFactory {

    @EachBean(EngineConfiguration)
    Engine buildEngine(EngineConfiguration engineConfiguration) {
        if (engineConfiguration.enabled) {
            (Engine){ -> engineConfiguration.cylinders }
        } else {
            throw new DisabledBeanException("Engine configuration disabled")
        }
    }
}
interface Engine {
    fun getCylinders(): Int
}

@EachProperty("engines")
class EngineConfiguration : Toggleable {

    var enabled = true

    @NotNull
    val cylinders: Int? = null

    override fun isEnabled(): Boolean {
        return enabled
    }
}

@Factory
class EngineFactory {

    @EachBean(EngineConfiguration::class)
    fun buildEngine(engineConfiguration: EngineConfiguration): Engine? {
        return if (engineConfiguration.isEnabled) {
            object : Engine {
                override fun getCylinders(): Int {
                    return engineConfiguration.cylinders!!
                }
            }
        } else {
            throw DisabledBeanException("Engine configuration disabled")
        }
    }
}

注入點(diǎn)

工廠的一個(gè)常見(jiàn)用例是從對(duì)象注入點(diǎn)開(kāi)始利用注釋元數(shù)據(jù),以便可以根據(jù)所述元數(shù)據(jù)修改行為。

考慮如下注釋:

 Java Groovy  Kotlin 
@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Cylinders {
    int value() default 8;
}
@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
@interface Cylinders {
    int value() default 8
}
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Cylinders(val value: Int = 8)

上面的注釋可用于自定義我們想要在定義注入點(diǎn)的位置注入載體的引擎類型:

 Java Groovy  Kotlin 
@Singleton
class Vehicle {

    private final Engine engine;

    Vehicle(@Cylinders(6) Engine engine) {
        this.engine = engine;
    }

    String start() {
        return engine.start();
    }
}
@Singleton
class Vehicle {

    private final Engine engine

    Vehicle(@Cylinders(6) Engine engine) {
        this.engine = engine
    }

    String start() {
        return engine.start()
    }
}
@Singleton
internal class Vehicle(@param:Cylinders(6) private val engine: Engine) {
    fun start(): String {
        return engine.start()
    }
}

The above Vehicle class specifies an annotation value of @Cylinders(6) indicating an Engine of six cylinders is required.(上面的 Vehicle 類指定了 @Cylinders(6) 的注釋值,指示需要六個(gè) cylinders 引擎。)

要實(shí)現(xiàn)此用例,請(qǐng)定義一個(gè)接受 InjectionPoint 實(shí)例的工廠來(lái)分析定義的注釋值:

 Java Groovy  Kotlin 
@Factory
class EngineFactory {

    @Prototype
    Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { // (1)
        final int cylinders = injectionPoint
                .getAnnotationMetadata()
                .intValue(Cylinders.class).orElse(8); // (2)
        switch (cylinders) { // (3)
            case 6:
                return new V6Engine(crankShaft);
            case 8:
                return new V8Engine(crankShaft);
            default:
                throw new IllegalArgumentException("Unsupported number of cylinders specified: " + cylinders);
        }
    }
}
@Factory
class EngineFactory {

    @Prototype
    Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { // (1)
        final int cylinders = injectionPoint
                .getAnnotationMetadata()
                .intValue(Cylinders.class).orElse(8) // (2)
        switch (cylinders) { // (3)
            case 6:
                return new V6Engine(crankShaft)
            case 8:
                return new V8Engine(crankShaft)
            default:
                throw new IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
        }
    }
}
@Factory
internal class EngineFactory {

    @Prototype
    fun v8Engine(injectionPoint: InjectionPoint<*>, crankShaft: CrankShaft): Engine { // (1)
        val cylinders = injectionPoint
                .annotationMetadata
                .intValue(Cylinders::class.java).orElse(8) // (2)
        return when (cylinders) { // (3)
            6 -> V6Engine(crankShaft)
            8 -> V8Engine(crankShaft)
            else -> throw IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
        }
    }
}
  1. 工廠方法定義了一個(gè) InjectionPoint 類型的參數(shù)。

  2. 注解元數(shù)據(jù)用于獲取@Cylinder注解的值

  3. 該值用于構(gòu)造引擎,如果無(wú)法構(gòu)造引擎,則拋出異常。

重要的是要注意工廠被聲明為@Prototype 范圍,因此為每個(gè)注入點(diǎn)調(diào)用該方法。如果要求 V8Engine 和 V6Engine 類型是單例,工廠應(yīng)該使用 Map 來(lái)確保對(duì)象只構(gòu)造一次。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)