Tuesday, April 21, 2015

spring annotations i never had the chance to use part 1: @primary

Today I remembered an old friend of mine (@primary) with whom we met from tutorials to tutorials. You know that in Spring @Autowired annotation works by type, that is if Spring finds an eligible bean that matches in terms of type it will inject it in. Let's see it on an example.

Assume I have two Singer classes; OperaSinger and MetalSinger.


@Component
public class MetalSinger implements Singer{

    @Override
    public String sing(String lyrics) {
        return "I am singing with DIO voice: "+lyrics;
    }
}
public class OperaSinger implements Singer {
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}
They both implement the Singer interface.
public interface Singer {
    String sing(String lyrics);
}
And lets define a SingerService and inject the Singer bean inside.
@Component
public class SingerService {
    private static final Logger logger = LoggerFactory.getLogger(SingerService.class);

    @Autowired
    private Singer singer;

    public String sing(){
        return singer.sing("song lyrics");
    }
}
What do you think; which Singer will be injected inside? Here's the result:
 I am singing with DIO voice: song lyrics.

This is because OperaSinger is not defined as Component or Service so Spring does not have a clue about it. If we add @Component annotion to it:


@Component
public class OperaSinger implements Singer {
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}
Than I'll get this exception:

 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [main.service.Singer] is defined: expected single matching bean but found 2: metalSinger,operaSinger

The reason is quite plain to see. If I have more than one bean with same type, and if I use @Autowired annotion which binds type I'll have this exception. Spring does not have a clue which Singer it should use.

Let's favor a music genre and tell Spring to use OperaSinger as Primary.


@Primary
@Component
public class OperaSinger implements Singer{

    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}
If we do the SingerService call we will get:
"I am singing in Bocelli voice: song lyrics"
That is because we choose OperaSinger as Primary which means "if you get confused with types you better use this one". Another approach would be the use of qualifier names which directly maps names to beans.