Spring series, part 2: @Qualifier and @Resource
Last time I talked about @Primary and @Autowire as a useful means of dependency injection. Today I will brief you on how @Autowire compares to its more standard counterpart @Resource and demonstrate @Qualifier as a handy complement to @Autowire.
Both @Autowire and @Resource serve the same purpose, i.e. finding the right bean, but work in a different way.
@Autowire is a framework-specific feature and relies on a data type rather than on a bean identifier. That makes it convenient to use, but presents a risk of run-time exceptions in case there is more than a single suitable candidate for dependency injection. As I showed in my previous post the risk can be decreased by using @Primary, but surely there are pitfalls too.
@Resource in contrast to @Autowire is a standard annotation (JSR-250) and looks up the relevant bean by name. Whereas @Autowire saves some typing, @Resource is very specific and eliminates any confusion about which bean should be injected.
Let’s set a “fruitful” example.
public interface IFruit { String whoAmI(); }
An apple, a banana and a lazy lemon:
@Service(″apple″) public class Apple implements IFruit { @Override public String whoAmI() { return ″apple″; } }
@Service(″banana″) public class Banana implements IFruit { @Override public String whoAmI() { return ″banana″; } }
@Service(″lemon″) @Lazy public class Lemon implements IFruit { @Override public String whoAmI() { return ″lemon″; } }
Now, the test below is bound to fail. That reminds us that @Autowire is indeed datatype-driven. In this case, there are three suitable implementations and Spring can't really tell which one to use:
import junit.framework.Assert; 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.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(″classpath:spring-config.xml″) public class FruitAppTests { @Autowired private IFruit fruit; @Test public void anAppleShouldBeAutowired() { Assert.assertEquals(″apple″, fruit.whoAmI()); } }
A failure comes as no surprise:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [IFruit] is defined: expected single matching bean but found 3: apple,banana,lemon
However, if we simply rename the the variable from fruit to apple, Spring is smart enough to pick the right bean(!):
.. @Autowired private IFruit apple; @Test public void anAppleShouldBeAutowired() { Assert.assertEquals(″apple″, apple.whoAmI()); } ..
Convention over configuration:
org.springframework.beans.factory.support.DefaultListableBeanFactory - Retrieved dependent beans for bean 'apple': [FruitAppTests]
For the sake of the exercise let’s revert the variable name back to fruit and use @Qualifier to help the framework resolve the bean:
.. import org.springframework.beans.factory.annotation.Qualifier; .. @Qualifier(″apple″) @Autowired private IFruit fruit; ..
The correct bean is once again successfully found and injected. The same can be achieved by using @Resource:
.. import javax.annotation.Resource; .. @Resource(name = ″banana″) private IFruit fruit2; @Test public void aBananaShouldBeTheResource() { Assert.assertEquals(″banana″, fruit2.whoAmI()); } ..
Finally, I wanted to touch the topic of a postponed bean creation. In the previous post, things didn’t go that well and we witnessed a premature instantiation despite having @Lazy in place. This time though, there is no trace of the lazily loaded bean (Lemon):
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'apple' org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'banana'
Great, but is the @Lazy actually needed? The ‘lemon’ bean is never used in our tests anyway. Let’s see what happens when the @Lazy is removed:
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'lemon' org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'lemon' to allow for resolving potential circular references
As you can see skipping @Lazy triggers the bean load, even though the bean is never referenced. I will talk about @Lazy in detail in my next post. Stay tuned.
Source Code
Previous: Part 1 – @Primary Next: Part 3 – @Lazy