In a Spring application, beans have names and can effectively be overridden simply by creating a bean with the same name, regardless of the type of the bean. Spring also has the notion of bean registration order, hence in Spring Boot you have and @AutoConfigureAfter that control how beans override each other.

    This strategy leads to difficult to debug problems, for example:

    • Bean loading order changes, leading to unexpected results

    • A bean with the same name overrides another bean with a different type

    To avoid these problems, Micronaut’s DI has no concept of bean names or load order. Beans have a type and a . You cannot override a bean of a completely different type with another.

    A useful benefit of Spring’s approach is that it allows overriding existing beans to customize behaviour. In order to support the same ability, Micronaut’s DI provides an explicit @Replaces annotation, which integrates nicely with support for and clearly documents and expresses the intention of the developer.

    Any existing bean can be replaced by another bean that declares @Replaces. For example, consider the following class:

    JdbcBookService

    JdbcBookService

    1. @Singleton
    2. @Requires(beans = DataSource.class)
    3. @Requires(property = "datasource.url")
    4. class JdbcBookService implements BookService {
    5. DataSource dataSource

    JdbcBookService

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

    Using @Replaces

    1. @Replaces(JdbcBookService.class) (1)
    2. @Singleton
    3. public class MockBookService implements BookService {
    4. Map<String, Book> bookMap = new LinkedHashMap<>();
    5. @Override
    6. public Book findBook(String title) {
    7. return bookMap.get(title);
    8. }
    9. }

    Using @Replaces

    1. @Replaces(JdbcBookService.class) (1)
    2. @Singleton
    3. class MockBookService implements BookService {
    4. Map<String, Book> bookMap = [:]
    5. @Override
    6. Book findBook(String title) {
    7. bookMap.get(title)
    8. }
    9. }

    Using @Replaces

    1. @Replaces(JdbcBookService::class) (1)
    2. @Singleton
    3. class MockBookService : BookService {
    4. var bookMap: Map<String, Book> = LinkedHashMap()
    5. override fun findBook(title: String): Book? {
    6. return bookMap[title]
    7. }
    8. }

    The @Replaces annotation also supports a factory argument. That argument allows the replacement of factory beans in their entirety or specific types created by the factory.

    For example, it may be desired to replace all or part of the given factory class:

    BookFactory

    1. @Factory
    2. public class BookFactory {
    3. @Singleton
    4. Book novel() {
    5. return new Book("A Great Novel");
    6. }
    7. @Singleton
    8. TextBook textBook() {
    9. return new TextBook("Learning 101");
    10. }

    BookFactory

    1. class BookFactory {
    2. @Singleton
    3. Book novel() {
    4. new Book('A Great Novel')
    5. }
    6. @Singleton
    7. TextBook textBook() {
    8. new TextBook('Learning 101')
    9. }
    10. }

    BookFactory

    To replace a factory in its entirety, it is necessary that your factory methods match the return types of all of the methods in the replaced factory.

    In this example, the BookFactory#textBook() will not be replaced because this factory does not have a factory method that returns a TextBook.

    CustomBookFactory

    1. @Factory
    2. @Replaces(factory = BookFactory.class)
    3. public class CustomBookFactory {
    4. @Singleton
    5. Book otherNovel() {
    6. return new Book("An OK Novel");
    7. }
    8. }
    1. @Factory
    2. @Replaces(factory = BookFactory.class)
    3. class CustomBookFactory {
    4. @Singleton
    5. Book otherNovel() {
    6. new Book('An OK Novel')
    7. }
    8. }

    CustomBookFactory

    1. @Factory
    2. @Replaces(factory = BookFactory::class)
    3. class CustomBookFactory {
    4. @Singleton
    5. internal fun otherNovel(): Book {
    6. return Book("An OK Novel")
    7. }
    8. }

    It may be the case that you don’t wish for the factory methods to be replaced, except for a select few. For that use case, you can apply the @Replaces annotation on the method and denote the factory that it should apply to.

    TextBookFactory

    1. @Factory
    2. public class TextBookFactory {
    3. @Singleton
    4. @Replaces(value = TextBook.class, factory = BookFactory.class)
    5. TextBook textBook() {
    6. return new TextBook("Learning 305");
    7. }
    8. }

    TextBookFactory

    1. @Factory
    2. class TextBookFactory {
    3. @Singleton
    4. @Replaces(value = TextBook.class, factory = BookFactory.class)
    5. TextBook textBook() {
    6. new TextBook('Learning 305')
    7. }

    TextBookFactory

    1. @Factory
    2. @Singleton
    3. @Replaces(value = TextBook::class, factory = BookFactory::class)
    4. internal fun textBook(): TextBook {
    5. return TextBook("Learning 305")
    6. }
    7. }

    The BookFactory#novel() method will not be replaced because the TextBook class is defined in the annotation.

    Default Implementation

    When exposing an API, it may be desirable for the default implementation of an interface to not be exposed as part of the public API. Doing so would prevent users from being able to replace the implementation because they would not be able to reference the class. The solution is to annotate the interface with DefaultImplementation to indicate which implementation should be replaced if a user creates a bean that @Replaces(YourInterface.class).

    For example consider:

    A public API contract

    1. import io.micronaut.context.annotation.DefaultImplementation;
    2. @DefaultImplementation(DefaultResponseStrategy.class)
    3. public interface ResponseStrategy {
    4. }
    1. import io.micronaut.context.annotation.DefaultImplementation
    2. @DefaultImplementation(DefaultResponseStrategy::class)
    3. interface ResponseStrategy

    The default implementation

    1. import javax.inject.Singleton;
    2. @Singleton
    3. class DefaultResponseStrategy implements ResponseStrategy {
    4. }
    1. import javax.inject.Singleton
    2. @Singleton
    3. class DefaultResponseStrategy implements ResponseStrategy {
    4. }
    1. import javax.inject.Singleton
    2. @Singleton
    3. internal class DefaultResponseStrategy : ResponseStrategy
    1. import io.micronaut.context.annotation.Replaces;
    2. import javax.inject.Singleton;
    3. @Singleton
    4. @Replaces(ResponseStrategy.class)
    5. public class CustomResponseStrategy implements ResponseStrategy {
    6. }
    1. import io.micronaut.context.annotation.Replaces
    2. import javax.inject.Singleton
    3. @Singleton
    4. @Replaces(ResponseStrategy)
    5. class CustomResponseStrategy implements ResponseStrategy {
    6. }
    1. import io.micronaut.context.annotation.Replaces
    2. import javax.inject.Singleton
    3. @Singleton
    4. @Replaces(ResponseStrategy::class)
    5. class CustomResponseStrategy : ResponseStrategy

    In the above example, the CustomResponseStrategy will replace the DefaultResponseStrategy because the annotation points to the DefaultResponseStrategy.