19

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.

SingleShot
  • 18,821
  • 13
  • 71
  • 101
  • 1
    Why don't you create FactoryCar bean. Then reference that factory within your carDriver. – chris Mar 29 '11 at 00:24

7 Answers7

18

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();
}
EliuX
  • 11,389
  • 6
  • 45
  • 40
8

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

Roy Truelove
  • 22,016
  • 18
  • 111
  • 153
Eugene Ryzhikov
  • 17,131
  • 3
  • 38
  • 60
7

With Spring 3.0.7

<bean id="carDriver" class="Driver">
   <property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
</bean>
vdr
  • 958
  • 6
  • 7
  • For me, I needed to do the following SpEL: #{ getBeanFactory().containsBean('customCar') ? customCar : defaultCar }` – Blaine May 28 '14 at 16:21
5

I'm not sure but probably declaring custom bean with primary="true" might help you.

Slava Semushin
  • 14,904
  • 7
  • 53
  • 69
4

Use JavaConfig:

@Configuration
public class CarConfig {

  @Autowired(required=false) @Qualifier("custom")
  Car customCar;

  @Autowired @Qualifier("default")
  Car defaultCar;

  @Bean
  public Car car() {
    return customCar != null ? customCar : defaultCar;
  }
}  

and

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

<!-- customCar defined somewhere else -->

<bean id="carDriver" class="Driver">
  <property name="car" ref="car"/>
</bean> 
sourcedelica
  • 23,940
  • 7
  • 66
  • 74
1

spring-boot-starter 1.4.0.RELEASE (spring-core 4.3.2.RELEASE)
or you could do like this:

public interface SomeService {
}
------------------------------------------------------------------------    
public interface CustomSomeService extends SomeService {
}
------------------------------------------------------------------------    
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;

@Service
@ConditionalOnMissingBean(CustomSomeService.class)
public class DefaultSomeService implements SomeService {
}
------------------------------------------------------------------------    
import org.springframework.stereotype.Service;

@Service
public class AdvancedSomeService implements CustomSomeService {
}
------------------------------------------------------------------------

class Application{

@Autowired
private SomeService someService;
/*
 Now if ApplicationContext contains CustomSomeService implementation 
'someService' use custom implementation. If CustomSomeService is 
missing 'someService' contains DefaultSomeService implementation.
*/
}
------------------------------------------------------------------------

import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { DefaultSomeService.class, AdvancedSomeService.class })
public class SomeServiceTest {

    @Autowired
    private SomeService someService;

    @Test
    public void test() {
        assertTrue(AdvancedSomeService.class.isInstance(someService));
    }

}

------------------------------------------------------------------------

import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { DefaultSomeService.class})
public class SomeServiceTest {

    @Autowired
    private SomeService someService;

    @Test
    public void test() {
        assertTrue(DefaultSomeService.class.isInstance(someService));
    }

}
Juha Hanka
  • 639
  • 6
  • 5
  • 1
    From JavaDoc of "ConditionalOnMissingBean": _The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only._ – stolsvik May 13 '19 at 22:05
1

With the newest Spring version you can use SpEL-based definition of your default value:

@Required
@Value("#{new com.my.company.DefaultStrategy()}")
public void setStrategy(final MyStrategy strategy) {
    this.strategy = strategy;
}

If you set this property from Spring context, bean that you defined in context will be injected. Otherwise, container injects bean specified by @Value annotation.

omnomnom
  • 8,911
  • 4
  • 41
  • 50