Introduction
Autowired
is great annotation, which by default inject beans by type to annotated element (constructor, setter or field). But how to use it, when there is more than one bean of requested type.
Autowired
with one bean
Suppose we will work with small interface:
interface IHeaderPrinter { String printHeader(String header) }
When we have only one bean implementing IHeaderPrinter
:
@Component class HtmlHeaderPrinter implements IHeaderPrinter{ @Override String printHeader(String header) { return "<h1>$header</h1>" } }
then everything works great and test passes.
@Autowired IHeaderPrinter headerPrinter @Test void shouldPrintHtmlHeader() { assert headerPrinter.printHeader('myTitle') == '<h1>myTitle</h1>' }
Two implementations
But what will happen, if we add another implementation of IHeaderPrinter
, e. g. MarkdownHeaderPrinter
?
@Component class MarkdownHeaderPrinter implements IHeaderPrinter { @Override String printHeader(String header) { return "# $header" } }
Now out test with fail with exception:
Error creating bean with name 'com.blogspot.przybyszd.spring.autowire.SpringAutowireWithQualifiersApplicationTests': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.blogspot.przybyszd.spring.autowire.IHeaderPrinter com.blogspot.przybyszd.spring.autowire.SpringAutowireWithQualifiersApplicationTests.headerPrinter; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.blogspot.przybyszd.spring.autowire.IHeaderPrinter] is defined: expected single matching bean but found 2: markdownHeaderPrinter,htmlHeaderPrinter
We have to decide which implementation we want to use in our test, so ...
Two implementations with Qualifier
Each bean is registered with name equal its class. For example HtmlHeaderPrinter
is named htmlHeaderPrinter
. The name is also its qualifier. We have to tell Autowired
, that it should inject htmlHeaderPrinter
:
@Autowired @Qualifier('htmlHeaderPrinter') IHeaderPrinter headerPrinter
Now our test passes again.
Two implementations qualified by field name
If field is names like implementing class (for example htmlHeaderPrinter
), then this class implementation will be injected:
@Autowired IHeaderPrinter htmlHeaderPrinter
And test passes:
@Test void shouldPrintHtmlHeader() { assert htmlHeaderPrinter.printHeader('myTitle') == '<h1>myTitle</h1>' }
Thanks to @marcinjasion
.
Two implementation with Primary
We often have one implementation which we almost always want to inject, so do we still have to put Qualifier
with its name wherever we want to use it? No.
We could mark one implementation as Primary
and this bean will be wired by default (unless we explicit give another Qualifier
to use injection point):
@Component @Primary class HtmlHeaderPrinter implements IHeaderPrinter{ // ... }
@Autowired IHeaderPrinter headerPrinter
Summary
Autowired
annotation allows us to inject dependencies to beans. It works great without additional configuration, when each bean could be uniquely find by type. When we have more than one bean, that could be injected, we have to use Qualifier
or Primary
annotation to help it find desired implementation.
Source code is available here.
No comments:
Post a Comment