【问题标题】:Spring 3: Inject Default Bean Unless Another Bean PresentSpring 3:除非另一个 Bean 存在,否则注入默认 Bean
【发布时间】:2011-07-24 21:36:04
【问题描述】:

我想通过 XML 配置 Spring,如果存在特定的 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 执行此操作,但除了包含自定义 bean 的文件之外,我不想提供属性文件/VM 属性/环境变量等。谢谢!


更新:

基于“使用工厂 bean”cmets,我对此进行了研究并提出了以下解决方案。首先,我创建了一个通用工厂 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,但这很有效。或许添加一个自定义架构元素会更简洁。

感谢其他 cmets。

【问题讨论】:

  • 为什么不创建 FactoryCar bean。然后在您的 carDriver 中引用该工厂。

标签: java xml spring spring-el


【解决方案1】:

【讨论】:

  • 你可以添加一个简单的例子
【解决方案2】:

使用 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> 

【讨论】:

    【解决方案3】:

    使用最新的 Spring 版本,您可以使用基于 SpEL 的默认值定义:

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

    如果你从 Spring 上下文中设置这个属性,你在上下文中定义的 bean 将被注入。否则,容器注入@Value注解指定的bean。

    【讨论】:

      【解决方案4】:

      我不确定,但可能使用 primary="true" 声明自定义 bean 可能会对您有所帮助。

      【讨论】:

      • Primary=true 是这个用例的最佳选择。
      • @primary 作为注释
      • 有没有办法让非自定义 bean 成为次要的?
      【解决方案5】:

      使用 Spring 3.0.7

      <bean id="carDriver" class="Driver">
         <property name="car" value="#{ getBeanFactory().containsBean('customCar') ? getBeanFactory().getBean('customCar') : defaultCar }"/>
      </bean>
      

      【讨论】:

      • 对我来说,我需要执行以下 SpEL:#{ getBeanFactory().containsBean('customCar') ? customCar : defaultCar }`
      【解决方案6】:

      您可以使用@Qualifier 选择一个版本的 Car(自定义或默认),但您应该知道您要使用的具体名称,并且您可能只想使用:

       @Autowired
       private Car car;
      

      您也可以使用@Primary 来解决这个问题,但它只是提供了一个避免歧义的偏好,并且会创建不需要的版本。 所以我建议使用注释

      org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
      

      因此,如果没有创建另一个 bean,您只会实例化一个 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();
      }
      

      【讨论】:

      • @ConditionalOnMissingBean 仅在使用 Spring Boot 时有效。
      【解决方案7】:

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

      【讨论】:

      • From JavaDoc of "ConditionalOnMissingBean": 条件只能匹配到目前应用上下文已经处理过的bean定义,因此强烈建议在仅限自动配置类。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-24
      • 2017-06-18
      • 1970-01-01
      • 1970-01-01
      • 2014-05-25
      • 2011-12-28
      相关资源
      最近更新 更多