Ответ 1
Использование FactoryBean - это самое простое решение - вы можете описать любой алгоритм, который вы хотите. Дополнительная информация находится на
Я хотел бы настроить Spring через XML таким образом, что если существует конкретный bean, он будет введен в целевой bean. Если он не существует, будет введен другой, по умолчанию bean.
Например, если у меня есть такой файл
<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>
И загрузите его, я бы хотел, чтобы defaultCar
был введен в драйвер. Однако, если я также загружаю следующий файл:
<bean id="customCar" class="FlyingCar">
<property name="name" value="Rocket Car"/>
<property name="maxAltitude" value="80000"/>
</bean>
Я хотел бы использовать customCar
bean вместо defaultCar
bean. Моя первоначальная попытка не работает, но я думаю, что иллюстрирует то, что я пытаюсь достичь:
<bean id="carDriver" class="Driver">
<property name="car" value="#{ @customCar eq null ? 'defaultCar' : 'customCar' }"/>
</bean>
Я знаю, как это сделать с помощью PropertyPlaceholderConfigurer
, но я не хочу предоставлять файл свойств/свойства VM/переменную среды/т.д. в дополнение к файлу, который содержит пользовательский bean. Спасибо!
Update:
Исходя из комментариев "use a factory bean", я рассмотрел это и придумал следующее решение. Во-первых, я создал общий factory bean, который позволяет указать имя по умолчанию bean и переопределить bean имя:
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;
}
Чтобы настроить драйвер для моего примера, вы бы сделали следующее:
<bean id="carDriver" class="Driver">
<property name="car">
<bean class="DefaultOverrideFactoryBean">
<property name="defaultBeanName" value="defaultCar"/>
<property name="overrideBeanName" value="customCar"/>
</bean>
</property>
</bean>
Я бы предпочел использовать SpEL, но это работает. Возможно, добавление пользовательского элемента схемы woud сделает этот чист.
Дополнительные комментарии оценены.
Использование FactoryBean - это самое простое решение - вы можете описать любой алгоритм, который вы хотите. Дополнительная информация находится на
С Spring 3.0.7
<bean id="carDriver" class="Driver">
<property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
</bean>
Использовать 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;
}
}
и
<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>
Вы можете использовать @Qualifier для выбора одной версии автомобиля (пользовательский или по умолчанию), но вы должны знать конкретное имя того, что вы собираетесь использовать, и вы можете использовать только:
@Autowired
private Car car;
Вы также можете использовать @Primary для решения этой проблемы, но он просто дает предпочтение избегать двусмысленности и будет создан нежелательные версии. Поэтому я бы рекомендовал использовать аннотацию
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
Таким образом, вы создадите только один bean, если другой не создан. Его особенно полезно, когда beans объявлены в разных модулях.
//Core module creates a default Car
@Bean()
@ConditionalOnMissingBean(Car.class)
Car car()
{
return new DefaultCar();
}
и
//Car module creates the wanted prototype car
@Bean()
Car car()
{
return new Toyota();
}
Я не уверен, но, возможно, объявление пользовательского bean с primary="true"
может вам помочь.
С новейшей версией Spring вы можете использовать определение по умолчанию на основе SpEL:
@Required
@Value("#{new com.my.company.DefaultStrategy()}")
public void setStrategy(final MyStrategy strategy) {
this.strategy = strategy;
}
Если вы установите это свойство из контекста Spring, будет введено bean, которое вы определили в контексте. В противном случае контейнер вводит bean, указанный аннотацией @Value
.
spring -boot-starter 1.4.0.RELEASE(spring -core 4.3.2.RELEASE)
или вы могли бы сделать так:
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));
}
}