Using Spring 3.X.X I have 2 services annotated with @Primary and I created another configuration class where I want to use a "customised" version of one of the services.
For some reason I ignore while debugging the configuration class I see that it gets the correct dependencies but when I want to use it it has setup the wrong ones.
I find it easier to explain with code, here is an example:
Interface Foo:
public interface Foo {
String getMessage();
}
Primary implementation with a default message hardcoded:
@Primary
@Service("FooImpl")
public class FooImpl implements Foo {
private String message = "fooDefaultMessage";
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Bar interface:
public interface Bar {
void doBar();
}
Bar default implementation:
@Primary
@Service("BarImpl")
public class BarImpl implements Bar {
private Foo foo;
@Override
public void doBar() {
System.out.println(foo.getMessage());
}
public Foo getFoo() {
return foo;
}
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
}
So far so good, if I want to Inject a Bar I get everything as expected. Thing is I have a Configuration with the following:
@Configuration
public class BeanConfiguration {
@Bean
@Qualifier("readOnlyFoo")
Foo readOnlyFoo() {
FooImpl foo = new FooImpl();
foo.setMessage("a read only message");
return foo;
}
@Bean
@Qualifier("readOnlyBar")
Bar readOnlyBar() {
BarImpl bar = new BarImpl();
bar.setFoo(readOnlyFoo());
return bar;
}
}
When I want to inject a readOnlyBar I am getting the default Foo instead of the one set up here.
private Bar readOnlyBar;
@Autowired
@Qualifier("readOnlyBar")
public void setReadOnlyBar(Bar readOnlyBar) {
this.readOnlyBar = readOnlyBar;
}
...
readOnlyBar.doBar();
Has the Foo bean with "fooDefaultMessage". Why is that? How can I make sure Spring uses the Bean I set on the @Configuration?
Thanks in advance,
EDIT: Note that the same happens if instead of calling the readOnly() method in the @Configuration class you pass a @Qualified argument. i.e.:
@Configuration
public class BeanConfiguration {
@Bean
@Qualifier("readOnlyFoo")
Foo readOnlyFoo() {
FooImpl foo = new FooImpl();
foo.setMessage("a read only message");
return foo;
}
@Bean
@Qualifier("readOnlyBar")
Bar readOnlyBar(@Qualifier("readOnlyFoo") Foo readOnlyFoo) {
BarImpl bar = new BarImpl();
bar.setFoo(readOnlyFoo);
return bar;
}
}
If instead I try using Constructor dependency injection everything works as I want/expect but unfortunately I can't use that.
As @yaswanth pointed out in his answer, the problem was that the foo
property was overwritten by property injection happening after the creation of the bean.
One way to get around that is to use constructor injection for the BarImpl
instead of property injection. That would make your code look like...
@Primary @Service("BarImpl")
class BarImpl implements Bar
{
private Foo foo;
@Autowired
public BarImpl(Foo foo) {
this.foo = foo;
}
}
and your configuration would be...
@Configuration
class BeanConfiguration
{
@Bean @Qualifier("readOnlyFoo")
Foo readOnlyFoo() {
FooImpl foo = new FooImpl();
foo.setMessage("a read only message");
return foo;
}
@Bean @Qualifier("readOnlyBar")
Bar readOnlyBar(@Qualifier("readOnlyFoo") Foo readOnlyFoo) {
return new BarImpl(readOnlyFoo);
}
}
As a side track; you should also make sure to use dependency injection in you factory methods instead of calling the factory method explicitly, or you will end up with multiple instances of your beans...
Good luck with your future endeavours!
You cannot use @Autowired
on an instance variable and set that in an @Bean
method like you did. Part of spring life cycle goes this way
Bean definitions scanned => Bean instances created => Post Processors called => .....
In your example,
@Bean
@Qualifier("readOnlyBar")
Bar readOnlyBar() {
BarImpl bar = new BarImpl();
bar.setFoo(readOnlyFoo());
return bar;
}
readOnlyBar
bean gets created and because readOnlyFoo()
is called from within this method, readOnlyFoo
bean also gets instantiated. Till this point, readOnlyBar
has readOnlyFoo
in its instance variable. Once the bean is instantiated, AutowiredAnnotationBeanPostProcessor
is called which scans the class of bean (BarImpl
in this case) for any @Autowired
annotations on instance variables or methods. It finds them and tries to inject the beans into the corresponding variables using field injection or setter injection (wherever the @Autowired
annotation is present). In this case since we have @Autowired
setter and did not specify the @Qualifier
annotation, spring injects the @Primary
bean which is the defaultFooMessage
bean.
AFAIK, there is no straightforward way to configure in spring for @Bean
methods to take priority over Autowiring.
Instantiating all beans with @Bean
methods will fix the issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With