Весна 3: вводить компонент по умолчанию, если не присутствует другой компонент

Я хотел бы настроить 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>

Я бы хотел, чтобы bean-компонент customCar использовался вместо bean-компонента defaultCar. Моя первоначальная попытка не сработала, но я думаю, это иллюстрирует то, чего я пытаюсь достичь:

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

Я знаю, как это сделать с помощью PropertyPlaceholderConfigurer, но я не хочу, чтобы в дополнение к файлу, содержащему настраиваемый 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, но это работает. Возможно, добавление настраиваемого элемента схемы сделает это чище.

Дополнительные комментарии приветствуются.


person SingleShot    schedule 29.03.2011    source источник
comment
Почему бы вам не создать компонент FactoryCar. Затем укажите этот завод в своем carDriver.   -  person chris    schedule 29.03.2011


Ответы (7)


Использование FactoryBean - самое простое решение - вы можете описать любой алгоритм, какой захотите. Более подробная информация на

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

person Eugene Ryzhikov    schedule 29.03.2011
comment
Вы могли бы добавить простой пример - person Michael-O; 27.10.2020

Вы можете использовать @Qualifier, чтобы выбрать одну версию Car (пользовательскую или стандартную), но вы должны знать конкретное имя того, что вы собираетесь использовать, и вы можете использовать только:

 @Autowired
 private Car car;

Вы также можете использовать @Primary для решения этой проблемы, но это просто дает предпочтение, чтобы избежать двусмысленности, и будут созданы нежелательные версии. Поэтому я бы рекомендовал использовать аннотацию

org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean

Таким образом, вы активируете только один bean-компонент, если другой не будет создан. Это особенно полезно, когда bean-компоненты объявлены в разных модулях.

//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();
}
person EliuX    schedule 31.08.2015
comment
@ConditionalOnMissingBean будет работать только при использовании Spring Boot. - person Xdg; 17.11.2016

С Spring 3.0.7

<bean id="carDriver" class="Driver">
   <property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
</bean>
person vdr    schedule 21.02.2013
comment
Мне нужно было сделать следующий SpEL: # {getBeanFactory (). ContainsBean ('customCar')? customCar: defaultCar} ` - person Blaine; 28.05.2014

Я не уверен, но, вероятно, объявление пользовательского bean-компонента с помощью primary="true" может вам помочь.

person Slava Semushin    schedule 24.09.2011
comment
Primary = true - это путь для этого варианта использования. - person fbiville; 04.02.2015
comment
@primary как аннотация - person Dariush Jafari; 03.07.2018
comment
Есть ли способ сделать нестандартный компонент второстепенным? - person hipokito; 28.02.2019

Используйте 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> 
person sourcedelica    schedule 30.03.2011

В последней версии Spring вы можете использовать определение вашего значения по умолчанию на основе SpEL:

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

Если вы установите это свойство из контекста Spring, будет внедрен bean-компонент, который вы определили в контексте. В противном случае контейнер внедряет bean-компонент, указанный аннотацией @Value.

person omnomnom    schedule 21.09.2011

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));
    }

}
person Juha Hanka    schedule 19.09.2016
comment
Из JavaDoc ConditionalOnMissingBean: Условие может соответствовать только определениям bean-компонентов, которые до сих пор были обработаны контекстом приложения, и поэтому настоятельно рекомендуется использовать это условие только для классов автоконфигурации. - person stolsvik; 14.05.2019