Весна 3. Внедрение бина по умолчанию, если не присутствует другой бин - PullRequest
14 голосов
/ 29 марта 2011

Я хотел бы настроить Spring через XML так, чтобы, если конкретный бин существовал, он был внедрен в целевой бин. Если он не существует, будет добавлен другой компонент по умолчанию.

Например, если у меня есть такой файл

<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 использовался вместо бина defaultCar. Моя первоначальная попытка не работает, но я думаю, иллюстрирует то, чего я пытаюсь достичь:

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

Я знаю, как это сделать с PropertyPlaceholderConfigurer, но я не хочу указывать файл свойств / свойство VM / переменную среды / и т.д. в дополнение к файлу, содержащему пользовательский компонент. Спасибо!


Обновление:

Основываясь на комментариях «использовать фабричный бин», я изучил это и нашел следующее решение. Во-первых, я создал общий фабричный компонент, который позволяет вам указать имя компонента по умолчанию и переопределить имя компонента:

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

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

Ответы [ 7 ]

9 голосов
/ 31 августа 2015

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

 @Autowired
 private Car car;

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

org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean

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

//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();
}
7 голосов
/ 21 февраля 2013

с пружиной 3.0.7

<bean id="carDriver" class="Driver">
   <property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
</bean>
7 голосов
/ 29 марта 2011

Использование 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

4 голосов
/ 25 сентября 2011

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

4 голосов
/ 30 марта 2011

Использовать 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> 
1 голос
/ 19 сентября 2016

spring-boot-starter 1.4.0.RELEASE (ядро с пружиной 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));
    }

}
1 голос
/ 21 сентября 2011

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

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

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

...