在許多情況下,您可能希望將不屬于您的代碼庫(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)
}
}
|
bean 是從替換現(xiàn)有引擎的字段定義的。
載體被注入。
該代碼斷言已調(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
}
|
兩個(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 具有以下限制:
以編程方式禁用 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")
}
}
}
|
工廠方法定義了一個(gè) InjectionPoint 類型的參數(shù)。
注解元數(shù)據(jù)用于獲取@Cylinder注解的值
該值用于構(gòu)造引擎,如果無(wú)法構(gòu)造引擎,則拋出異常。
重要的是要注意工廠被聲明為@Prototype 范圍,因此為每個(gè)注入點(diǎn)調(diào)用該方法。如果要求 V8Engine 和 V6Engine 類型是單例,工廠應(yīng)該使用 Map 來(lái)確保對(duì)象只構(gòu)造一次。
更多建議: