阅读(3648) (0)

Micronaut Bean 替换

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

Micronaut 的依赖注入系统和 Spring 的一个显着区别是替换 bean 的方式。

在 Spring 应用程序中,bean 具有名称,并且通过创建具有相同名称的 bean 来覆盖,而不管 bean 的类型如何。 Spring 也有 bean 注册顺序的概念,因此在 Spring Boot 中你有 @AutoConfigureBefore 和 @AutoConfigureAfter 注释来控制 bean 如何相互覆盖。

这种策略会导致难以调试的问题,例如:

  • Bean加载顺序改变,导致意外结果

  • 具有相同名称的 bean 覆盖具有不同类型的另一个 bean

为了避免这些问题,Micronaut 的 DI 没有 bean 名称或加载顺序的概念。 Beans 有一个类型和一个限定符。您不能用另一个覆盖完全不同类型的 bean。

Spring 方法的一个有用好处是它允许覆盖现有 bean 以自定义行为。为了支持同样的能力,Micronaut 的 DI 提供了一个显式的 @Replaces 注解,它很好地集成了对条件 Bean 的支持,并清楚地记录和表达了开发人员的意图。

任何现有的 bean 都可以被另一个声明了 @Replaces 的 bean 替换。例如,考虑以下类:

JdbcBookService

 Java Groovy  Kotlin 
@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public class JdbcBookService implements BookService {

    DataSource dataSource;

    public JdbcBookService(DataSource dataSource) {
        this.dataSource = dataSource;
    }
@Singleton
@Requires(beans = DataSource)
@Requires(property = "datasource.url")
class JdbcBookService implements BookService {

    DataSource dataSource
@Singleton
@Requirements(Requires(beans = [DataSource::class]), Requires(property = "datasource.url"))
class JdbcBookService(internal var dataSource: DataSource) : BookService {

你可以在 src/test/java 中定义一个类来替换这个类只是为了你的测试:

Using @Replaces

 Java Groovy  Kotlin 
@Replaces(JdbcBookService.class) // (1)
@Singleton
public class MockBookService implements BookService {

    Map<String, Book> bookMap = new LinkedHashMap<>();

    @Override
    public Book findBook(String title) {
        return bookMap.get(title);
    }
}
@Replaces(JdbcBookService.class) // (1)
@Singleton
class MockBookService implements BookService {

    Map<String, Book> bookMap = [:]

    @Override
    Book findBook(String title) {
        bookMap.get(title)
    }
}
@Replaces(JdbcBookService::class) // (1)
@Singleton
class MockBookService : BookService {

    var bookMap: Map<String, Book> = LinkedHashMap()

    override fun findBook(title: String): Book? {
        return bookMap[title]
    }
}
  1. MockBookService 声明它替换 JdbcBookService

替换 Factory

@Replaces 注释还支持工厂参数。该参数允许替换整个工厂 bean 或工厂创建的特定类型。

例如,可能需要替换给定工厂类的全部或部分:

BookFactory

 Java Groovy  Kotlin 
@Factory
public class BookFactory {

    @Singleton
    Book novel() {
        return new Book("A Great Novel");
    }

    @Singleton
    TextBook textBook() {
        return new TextBook("Learning 101");
    }
}
@Factory
class BookFactory {

    @Singleton
    Book novel() {
        new Book('A Great Novel')
    }

    @Singleton
    TextBook textBook() {
        new TextBook('Learning 101')
    }
}
@Factory
class BookFactory {

    @Singleton
    internal fun novel(): Book {
        return Book("A Great Novel")
    }

    @Singleton
    internal fun textBook(): TextBook {
        return TextBook("Learning 101")
    }
}

要完全替换工厂,您的工厂方法必须与被替换工厂中所有方法的返回类型相匹配。

在此示例中,BookFactory#textBook() 未被替换,因为该工厂没有返回 TextBook 的工厂方法。

CustomBookFactory

 Java Groovy  Kotlin 
@Factory
@Replaces(factory = BookFactory.class)
public class CustomBookFactory {

    @Singleton
    Book otherNovel() {
        return new Book("An OK Novel");
    }
}
@Factory
@Replaces(factory = BookFactory)
class CustomBookFactory {

    @Singleton
    Book otherNovel() {
        new Book('An OK Novel')
    }
}
@Factory
@Replaces(factory = BookFactory::class)
class CustomBookFactory {

    @Singleton
    internal fun otherNovel(): Book {
        return Book("An OK Novel")
    }
}

要替换一个或多个工厂方法但保留其余方法,请在方法上应用 @Replaces 注释并表示要应用的工厂。

TextBookFactory

 Java Groovy  Kotlin 
@Factory
public class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook.class, factory = BookFactory.class)
    TextBook textBook() {
        return new TextBook("Learning 305");
    }
}
@Factory
class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook, factory = BookFactory)
    TextBook textBook() {
        new TextBook('Learning 305')
    }
}
@Factory
class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook::class, factory = BookFactory::class)
    internal fun textBook(): TextBook {
        return TextBook("Learning 305")
    }
}

BookFactory#novel() 方法不会被替换,因为 TextBook 类是在注解中定义的。

默认实现

公开 API 时,可能不希望将接口的默认实现公开为公共 API 的一部分。这样做会阻止用户替换实现,因为他们将无法引用该类。解决方案是用 DefaultImplementation 注释接口,以指示如果用户创建一个 @Replaces(YourInterface.class) 的 bean,要替换哪个实现。

例如考虑:

A public API contract

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.DefaultImplementation;

@DefaultImplementation(DefaultResponseStrategy.class)
public interface ResponseStrategy {
}
import io.micronaut.context.annotation.DefaultImplementation

@DefaultImplementation(DefaultResponseStrategy)
interface ResponseStrategy {
}
import io.micronaut.context.annotation.DefaultImplementation

@DefaultImplementation(DefaultResponseStrategy::class)
interface ResponseStrategy

默认实现

 Java Groovy  Kotlin 
import jakarta.inject.Singleton;

@Singleton
class DefaultResponseStrategy implements ResponseStrategy {

}
import jakarta.inject.Singleton

@Singleton
class DefaultResponseStrategy implements ResponseStrategy {

}
import jakarta.inject.Singleton

@Singleton
internal class DefaultResponseStrategy : ResponseStrategy

自定义实现

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

@Singleton
@Replaces(ResponseStrategy.class)
public class CustomResponseStrategy implements ResponseStrategy {

}
import io.micronaut.context.annotation.Replaces
import jakarta.inject.Singleton

@Singleton
@Replaces(ResponseStrategy)
class CustomResponseStrategy implements ResponseStrategy {

}
import io.micronaut.context.annotation.Replaces
import jakarta.inject.Singleton

@Singleton
@Replaces(ResponseStrategy::class)
class CustomResponseStrategy : ResponseStrategy

在上面的示例中,CustomResponseStrategy 替换了 DefaultResponseStrategy,因为 DefaultImplementation 注释指向 DefaultResponseStrategy。