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