Spring fallback bean implementation
Asked Answered
F

2

12

I'm currently trying to configure Spring Boot (using Java Annotations and ComponentScan) for the following scenario:

Scenario

  • There's an interface MyService.
  • I want to provide a default implementation for MyService, let's call it MyDefaultService.
  • If the component scan detects no other implementation for MyService, Spring should instantiate MyDefaultService as a "fallback".
  • If there is a different implementation of MyService present, let's say MyCustomService, then that bean should always take precedence over MyDefaultService when autowiring a dependency to MyService. In that regard, MyDefaultService should be recessive (as opposed to @Primary).
  • Ideally, there should not need to be an additional annotation on MyCustomService to have it "override" MyDefaultService.
  • Ideally, no explicitly implemented factories or factory methods should be required.

Question

The question is: how do I need to annotate the MyDefaultService class in order to achieve this?

What I tried so far to solve the problem

  • Annotating MyDefaultService with @ConditionalOnMissingBean(MyService.class). Didn't work because MyDefaultService is never used, even if there is no other implementation of MyService.
  • There is an annotation called @Primarythat solves the problem. However, it needs to reside on MyCustomService, a class that I try to keep free of additional annotations. Essentially, I need the inverse annotation of @Primary on MyDefaultService. However, I couldn't find such an annotation.

Concrete use case

I am developing a service layer in one project, and a different project will implement a web UI layer on top of it. The UI project has a dependency to the service layer project. However, for certain functionalities implemented at the service layer, I need to know which user is currently logged in at the web context. So I have to define a service interface for that in the service layer project, such that it can be implemented by the UI project. However, for testing purposes in the service-layer project, I need a default implementation of that interface. Also, in case that the UI project team forgets to implement this interface, the app should not crash, but instead instantiate the fallback bean and issue a warning.

Thanks & kind regards,

Alan

Fealty answered 1/1, 2015 at 23:40 Comment(0)
W
3

Traditionally in Spring you could use @Primary annotation to mark a bean, so that it would take precedence over regular beans not annotated with @Primary in case of multiple autowiring candidates are found.

However, in Spring 6.2 a new annotation was introduced - @Fallback.

It is essentially the opposite of @Primary. Such bean would be used only in case there are no beans with @Primary annotation AND no regular beans.

Walkout answered 19/6 at 19:10 Comment(0)
L
9

I suggest writing an implementation of FactoryBean to do this. Your FactoryBean would scan the bean factory looking for beans that implement MyService, and if it finds one it returns that bean from getObject. If it doesn't, then it can instantiate MyDefaultService directly and return that. Your factory bean then gets annotated with @Primary.

So pieces like this (pseudo-code):

public class MyServiceFactory implements FactoryBean<MyService> {
    ListableBeanFactory beanFactory;

    public MyService getObject() {
        Map beans = beanFactory.getBeansOfType(MyService.class)
        if (beans.isEmpty())
            return new MyDefaultService(); // plus args, obviously
        else
            return get_some_bean_from_the_map
    }
}

and then

@Primary
@Bean
public MyServiceFactory MyServiceFactory() {
    return new MyServiceFactory();
}

Spring will automatically handle the factory bean (i.e. it will make the MyService object available as a bean for injection like normal.

This solution doesn't require any special magic, and it's fairly obvious how it works. You can also handle errant cases such as multiple MyService beans being declared.

Langdon answered 2/1, 2015 at 0:29 Comment(1)
ListableBeanFactory beanFactory in MyServiceFactory is null. How to work it out?Batchelor
W
3

Traditionally in Spring you could use @Primary annotation to mark a bean, so that it would take precedence over regular beans not annotated with @Primary in case of multiple autowiring candidates are found.

However, in Spring 6.2 a new annotation was introduced - @Fallback.

It is essentially the opposite of @Primary. Such bean would be used only in case there are no beans with @Primary annotation AND no regular beans.

Walkout answered 19/6 at 19:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.