Reputation: 19131
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.
Upvotes: 20
Views: 30419
Reputation: 659
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));
}
}
Upvotes: 1
Reputation: 12625
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();
}
Upvotes: 20
Reputation: 17359
Using FactoryBean is the simplest solution - you can describe any algorithm you want. More information is at
Upvotes: 8
Reputation: 988
With Spring 3.0.7
<bean id="carDriver" class="Driver">
<property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
</bean>
Upvotes: 7
Reputation: 15204
I'm not sure but probably declaring custom bean with primary="true"
might help you.
Upvotes: 5
Reputation: 9139
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.
Upvotes: 1
Reputation: 24040
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>
Upvotes: 5