|

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

Similar Posts