Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring 3: Inject Default Bean Unless Another Bean Present

I would like to configure Spring via XML such that if a particular bean exists, it will be injected into the target bean. If it does not exist, a different, default bean, will be injected.

For example if I have a file like this

<bean id="carDriver" class="Driver">
  <property name="car" value="SOME EXPRESSION GOES HERE, SEE ATTEMPT BELOW"/>
</bean>

<bead id="defaultCar" class="Car">
  <property name="name" value="Honda Accord"/>
</bean>

And load it, I would like the defaultCar injected into the driver. However, if I also load the following file:

<bean id="customCar" class="FlyingCar">
  <property name="name" value="Rocket Car"/>
  <property name="maxAltitude" value="80000"/>
</bean>

I would want the customCar bean to be used instead of the defaultCar bean. My initial attempt does not work, but I think illustrates what I'm trying to achieve:

<bean id="carDriver" class="Driver">
  <property name="car" value="#{ @customCar eq null ? 'defaultCar' : 'customCar' }"/>
</bean>

I know how to do this with a PropertyPlaceholderConfigurer, but I don't want to have to provide a property file / VM property / environment variable / etc. in addition to the file that contains the custom bean. Thanks!


Update:

Based on the "use a factory bean" comments, I looked into this and came up with the following solution. First, I created a generic factory bean that allows you to specify a default bean name and an override bean name:

public class DefaultOverrideFactoryBean implements FactoryBean, BeanFactoryAware {

    public Object getObject() throws Exception {
        return beanFactory.containsBean(overrideBeanName) ?
               beanFactory.getBean(overrideBeanName)      :
               beanFactory.getBean(defaultBeanName);
    }

    public Class<?> getObjectType() {
        return Object.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void setDefaultBeanName(String defaultBeanName) {
        this.defaultBeanName = defaultBeanName;
    }

    public void setOverrideBeanName(String overrideBeanName) {
        this.overrideBeanName = overrideBeanName;
    }

    private String defaultBeanName;
    private String overrideBeanName;
    private BeanFactory beanFactory;
}

To configure my example car driver, you would do this:

<bean id="carDriver" class="Driver">
  <property name="car">
    <bean class="DefaultOverrideFactoryBean">
      <property name="defaultBeanName" value="defaultCar"/>
      <property name="overrideBeanName" value="customCar"/>
    </bean>
  </property>
</bean>

I would have preferred to use SpEL, but this works. Perhaps adding a custom schema element woud make this cleaner.

Additional comments appreciated.

like image 846
SingleShot Avatar asked Mar 29 '11 00:03

SingleShot


People also ask

How do I inject an existing bean to another bean?

For a class annotated with @Component , specifying them is required for the autowired constructor but in a @Bean declaration you don't need to provide a parameter to specify the MyObject dependency to use (while it will work) if that is accessible in the current class, which is your case.

How do you inject specific beans in a Spring boot?

In Spring Boot, we can use Spring Framework to define our beans and their dependency injection. The @ComponentScan annotation is used to find beans and the corresponding injected with @Autowired annotation. If you followed the Spring Boot typical layout, no need to specify any arguments for @ComponentScan annotation.

Can two beans make the same instance?

Here, @Bean instantiates two beans with ids the same as the method names and registers them within the BeanFactory (Spring container) interface. Next, we can initialize the Spring container and request any of the beans from the Spring container. This strategy also makes it simple to achieve dependency injection.


2 Answers

You may used @Qualifier to choose one version of Car (custom or default), but you shall know the specific name of what you gonna use, and you may want to use just:

 @Autowired
 private Car car;

You may also use @Primary to solve this, but it just gives a preference to avoid ambiguity and it will be created the unwanted versions. So i would recomend to use the annotation

org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean

So you will only instantate one bean if another is not created. Its specially usefull when the beans are declared in differents modules.

//Core module creates a default Car
@Bean()
@ConditionalOnMissingBean(Car.class)
Car car()
{
  return new DefaultCar();
}

and

//Car module creates the wanted prototype car
@Bean()
Car car()
{
  return new Toyota();
}
like image 88
EliuX Avatar answered Oct 22 '22 14:10

EliuX


Using FactoryBean is the simplest solution - you can describe any algorithm you want. More information is at

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/FactoryBean.html

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-extension-factorybean

like image 33
Eugene Ryzhikov Avatar answered Oct 22 '22 14:10

Eugene Ryzhikov