java Spring 3:除非另一个 Bean 存在,否则注入默认 Bean

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5466462/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 11:15:10  来源:igfitidea点击:

Spring 3: Inject Default Bean Unless Another Bean Present

javaxmlspringspring-el

提问by SingleShot

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.

我想通过 XML 配置 Spring,以便如果存在特定的 bean,它将被注入到目标 bean 中。如果它不存在,将注入一个不同的默认 bean。

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 defaultCarinjected into the driver. However, if I also load the following file:

并加载它,我想defaultCar注入驱动程序。但是,如果我还加载以下文件:

<bean id="customCar" class="FlyingCar">
  <property name="name" value="Rocket Car"/>
  <property name="maxAltitude" value="80000"/>
</bean>

I would want the customCarbean to be used instead of the defaultCarbean. My initial attempt does not work, but I think illustrates what I'm trying to achieve:

我希望使用customCarbean 而不是defaultCarbean。我最初的尝试不起作用,但我认为说明了我想要实现的目标:

<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!

我知道如何使用 . 来执行此操作PropertyPlaceholderConfigurer,但除了包含自定义 bean 的文件之外,我不想还必须提供属性文件/VM 属性/环境变量等。谢谢!



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:

基于“使用工厂 bean”的评论,我对此进行了调查并提出了以下解决方案。首先,我创建了一个通用工厂 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;
}

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.

我更喜欢使用 SpEL,但这有效。也许添加一个自定义架构元素会让这更干净。

Additional comments appreciated.

补充意见表示赞赏。

回答by EliuX

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:

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

 @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

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

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.

因此,如果未创建另一个 bean,您将仅实例化一个 bean。当 bean 在不同的模块中声明时,它特别有用。

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

回答by vdr

With Spring 3.0.7

使用 Spring 3.0.7

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

回答by sourcedelica

Use JavaConfig:

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

回答by Slava Semushin

I'm not sure but probably declaring custom bean with primary="true"might help you.

我不确定,但可能声明自定义 beanprimary="true"可能对您有所帮助。

回答by Juha Hanka

spring-boot-starter 1.4.0.RELEASE (spring-core 4.3.2.RELEASE)
or you could do like this:

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

}

回答by omnomnom

With the newest Spring version you can use SpEL-based definition of your default value:

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

@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 @Valueannotation.

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