Question

Consider the following class:

public class MyBean {
    private A a;

    @Autowired(required=true)
    public void setA(A a) {
        this.a = a;
    }

    public A getA() {
        return a;
    }
}

There are cases when one need need to override the autowired injection, for example when Spring cannot find a single candidate for the injection. In XML I can have the following example:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="first" class="my.pkg.AImpl"/>
    <bean id="second" class="my.pkg.AImpl"/>
    <bean id="myBeanFirst" class="my.pkg.MyBean">
        <property name="a" ref="first"/>
    </bean>
    <bean id="myBeanSecond" class="my.pkg.MyBean">
        <property name="a" ref="second"/>
    </bean>

</beans>

Is there a way to do the same thing with Java Config? The following doesn't work (and I understand why) because Spring try to autowire the property after returning from myBean method and it fails with NoUniqueBeanDefinitionException:

@Configuration
public class MyConfig {
    @Bean
    public A first() {
        return new AImpl();
    }

    @Bean
    public A second() {
        return new AImpl();
    }

    @Bean
    public MyBean myBeanFirst(A first) {
        MyBean myBean = new MyBean();
        myBean.setA(first);
        return myBean;
    }

    @Bean
    public MyBean myBeanSecond(A second) {
        MyBean myBean = new MyBean();
        myBean.setA(first);
        return myBean;
    }
}

To modify the MyBean class isn't always an option, for example because it comes from an external library. Is this a case where I have to use XML configuration?

Thanks, Andrea Polci

Update Thanks for the two solution so fare (injection by name, and use of @Primary) but they don't solve my use case so I need to be more specific I think.

In my use case the MyBean class comes from an external library so any change to it is not possible. I also need to have more than one instance of MyBean, each one injecting different intances of the A interface. I've update the code above to reflect this (both xml and java).

Is there any solution using java config? Is it possible to avoid the autowire of the dependency on MyBean? (Only on the beans of that class, not disablig autowiring completely for every bean in the context)

Was it helpful?

Solution

Ok here it goes, I believe this answer will satisfy your needs.

What we need is an implementation of MergedBeanDefinitionPostProcessor that will set the correct value for the property a of class MyBean. This can be done by the following class

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.stereotype.Component;


@Component
public class MyBeanPostProcessor implements MergedBeanDefinitionPostProcessor {


    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if(beanName.equals("myBeanFirst")) {
            beanDefinition.getPropertyValues().add("a", getMyBeanFirstAImpl());
        }
        else if(beanName.equals("myBeanSecond")) {
            beanDefinition.getPropertyValues().add("a", getMyBeanSecondAImpl());
        }
    }

    private Object getMyBeanFirstAImpl() {
        return new AImpl();
    }

    private Object getMyBeanSecondAImpl() {
        return new AImpl();
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

As you can see the names of the beans here are hardcoded but they could be set in static final String that would be also used by the @Bean annotations in the following code

@Configuration
public class Configuration {



    @Bean
    public MyBean myBeanFirst() {
        return new MyBean();
    }

    @Bean
    public MyBean myBeanSecond() {
        return new MyBean();
    }

}

You will notice in the following code that setA is not called in the MyBean creation methods, because no matter what value we set (or in this case don't set) will be overridden when Spring executes the bean post processor.

In case you need a default value for A (if for example you are injecting it in other beans), proceed to define a @Bean in the previous configuration

OTHER TIPS

If you have multiple candidates for injection you can specify the default one by using @Primary on the bean you want to be the default AImpl. That way no changes are needed to MyBean

If you want myBeanFirst to use first, then just call first(). Same for myBeanSecond, want it to use second, then just call second() when setting A;

@Configuration
public class MyConfig {
    @Bean
    public A first() {
        return new AImpl();
    }

    @Bean
    public A second() {
        return new AImpl();
    }

    @Bean
    public MyBean myBeanFirst() {
        MyBean myBean = new MyBean();
        myBean.setA(first());
        return myBean;
    }

    @Bean
    public MyBean myBeanSecond() {
        MyBean myBean = new MyBean();
        myBean.setA(second());
        return myBean;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top