Micronaut 的依赖注入系统和 Spring 的一个显着区别是替换 bean 的方式。
在 Spring 应用程序中,bean 具有名称,并且通过创建具有相同名称的 bean 来覆盖,而不管 bean 的类型如何。 Spring 也有 bean 注册顺序的概念,因此在 Spring Boot 中你有 @AutoConfigureBefore 和 @AutoConfigureAfter 注释来控制 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]
}
}
|
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。
更多建议: