69

Has anyone tried to auto-wire different beans into a Spring-managed bean based on a condition? For e.g. if some condition is met, inject class A, else B? I saw in one of the Google search results that it is possible with SpEL (Spring Expression Language), but could not locate a working example.

Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
Paddy
  • 3,472
  • 5
  • 29
  • 48

4 Answers4

92

There are multiple ways to achieve this. Mostly this depends on the conditioning you want to perform.

Factory bean

You can implement simple factory bean to do the conditional wiring. Such factory bean can contain complex conditioning logic:

public MyBeanFactoryBean implements FactoryBean<MyBean> {

    // Using app context instead of bean references so that the unused 
    // dependency can be left uninitialized if it is lazily initialized
    @Autowired
    private ApplicationContext applicationContext;

    public MyBean getObject() {
        MyBean myBean = new MyBean();
        if (true /* some condition */) {
            myBean.setDependency(applicationContext.getBean(DependencyX.class));
        } else {
            myBean.setDependency(applicationContext.getBean(DependencyY.class));
        }
        return myBean;
    }

    // Implementation of isSingleton => false and getObjectType

}

Maybe a bit better approach is if you use factory bean to create the dependency bean in case you want to have only one such bean in your application context:

public MyDependencyFactoryBean implements FactoryBean<MyDependency> {

    public MyDependency getObject() {
        if (true /* some condition */) {
            return new MyDependencyX();
        } else {
            return new MyDependencyY();
        }
    }

    // Implementation of isSingleton => false and getObjectType

}

SpEL

With SpEL there are many possibilities. Most common are system property based conditions:

<bean class="com.example.MyBean">
    <property name="dependency" value="#{systemProperties['foo'] == 'bar' ? dependencyX : dependencyY}" />
</bean>

Property placeholder

You can have property placeholder resolve your bean reference. The dependency name can be part of the application configuration.

<bean class="com.example.MyBean">
    <property name="dependency" ref="${dependencyName}" />
</bean>

Spring profiles

Usually the condition you want to evaluate means that a whole set of beans should or should not be registered. Spring profiles can be used for this:

<!-- Default dependency which is referred by myBean -->
<bean id="dependency" class="com.example.DependencyX" />

<beans profile="myProfile">
    <!-- Override `dependency` definition if myProfile is active -->
    <bean id="dependency" class="com.example.DependencyY" />
</beans>

Other methods can mark the bean definition as lazy-init="true", but the definition will be still registered inside application context (and making your life harder when using unqualified autowiring). You can also use profiles with @Component based beans via @Profile annotation.

Check ApplicationContextInitialier (or this example) to see how you can activate profiles programatically (i.e. based on your condition).

Java config

This is why Java based config is being so popular as you can do:

@Bean
public MyBean myBean() {
    MyBean myBean = new MyBean();
    if (true /* some condition */) {
        myBean.setDependency(dependencyX());
    } else {
        myBean.setDependency(dependencyY());
    }
    return myBean;
}

Of course you can use more or less all configuration methods in the java based config as well (via @Profile, @Value or @Qualifier + @Autowired).

Post processor

Spring offers numerous hook points and SPIs, where you can participate in the application context life-cycle. This section requires a bit more knowledge of Spring's inner workings.

BeanFactoryPostProcessors can read and alter bean definitions (e.g. property placeholder ${} resolution is implemented this way).

BeanPostProcessors can process bean instances. It is possible to check freshly created bean and play with it (e.g. @Scheduled annotation processing is implemented this way).

MergedBeanDefinitionPostProcessor is extension of bean post processor and can alter the bean definition just before it is being instantiated (@Autowired annotation processing is implemented this way).


UPDATE Oct 2015

  • Spring 4 has added a new method how to do conditional bean registration via @Conditional annotation. That is worth checking as well.

  • Of course there are numerous other ways with Spring Boot alone via its @ConditionalOn*.

  • Also note that both @Import and @ComponentScan (and their XML counterparts) undergo property resolution (i.e. you can use ${}).

Pavel Horal
  • 17,782
  • 3
  • 65
  • 89
  • 1
    Good answer. I try to rely on _configuration parameters_ almost totally, with _conditional config_ in as few cases as possible (zero preferably, but occasionally this kinda technique was required for switching database infrastructure). Normally we keep a "developer" properties file with hostname/DB/email server etc, separate from the "production" properties.. and deploy that separately from the application. – Thomas W Oct 07 '13 at 21:59
  • Hi @Pavel, I want to use a base class or a subclass based on an attribute of another class. Basically these two are strategy classes and I don't want to make them Singletons myself, instead rely on Spring's singleton beans to plug one of them into the class whose attribute I am testing. Do not want to introduce Spring-specific code as much as possible (Profile looked good, but to inject programmatically, again Spring code seems necessary). Can you kindly guide as to the best approach in this case? Can something be worked out with Qualifiers and SPEL combination in annotations? – Paddy Oct 08 '13 at 03:46
  • You can invoke bean methods or check bean properties with SPeL. So it should be possible to alter the SPeL example above with `#{myConditionBean.myProp == 'someValue' ? dependencyX : dependencyY}`. – Pavel Horal Oct 08 '13 at 06:45
  • Does this have sideeffects when injecting into singleton beans? I'm guessing it should. Thoughts? – Zeus Jan 27 '17 at 18:41
  • Not sure I understand your concerns. Can you add more detail to the question? – Pavel Horal Jan 27 '17 at 18:42
  • Same need here, no solution at all with @Qualifier et al. – maxxyme Mar 22 '17 at 16:12
2

In your @Configuration class declare a bean to be conditionally created:

@Bean
@Conditional(CustomFeatureCondition.class)
public Stuff stuff() {
    return new Stuff ();
}

In the place of using just @Autowire it with required = false option:

@Component
@Setter(onMethod_ = @Autowired(required = false))
public class AnotherStuff {
    private Stuff stuff;
    // do stuff here
}

This way you'll get Stuff bean if it exists in the context and stuff = null if it doesn't.

Torino
  • 445
  • 5
  • 12
1

I had a case where I needed to inject different beans depending on property: "my.property". In my case this solution was successful:

 <property name="name" ref="#{ ${my.property:false}==true ? 'bean1' : 'bean2' }"/>

I needed to add the apostrophes around bean names in order to make it work.

fascynacja
  • 1,625
  • 4
  • 17
  • 35
1

I suppose the simpest way:

@Autowired @Lazy
protected A a;

@Autowired @Lazy
protected B b;

void do(){
  if(...) { // any condition
     // use a
  } else {
     // use b
  }
}

In case you do not declare nessassary bean, Spring throws at runtime NoSuchBeanDefinitionException

Grigory Kislin
  • 16,647
  • 10
  • 125
  • 197