I'm learning the basics of Spring and right now I'm going over Beans/wiring. This question might not make sense, it's just something I'm thinking about after reading/trying out some examples from the book Spring into Action by Craig Walls. Say there is this interface.
public interface CompactDisc {
void play()
}
And I have two classes that implement this interface.
public class HybridTheory implements CompactDisc { }
public class Meteora implements CompactDisc { }
My configuration class uses component scanning and creates beans from these two classes. Now if I had a test class that has an instance of Compact disc and is wired using autowired
public class myTest {
@Autowired
private CompactDisc cd;
}
there would be a problem correct? My question is, how do you wire it so that it uses one bean over the other? Or is this not a real situation/should I make the property of a specific class, not of the interface? I guess I'm just having difficulty wrapping my head around wiring.
Whenever you run into situation that you have more than one implementing same interface there are few approaches you can take.
1. Use @Qualifier annotation
If you use classpath scanning, your classes may look like:
@Component
public class HybridTheory implements CompactDisc { }
@Component
public class Meteora implements CompactDisc { }
Each Spring bean gets an id assigned by a container and by default its a class name starting with lower letter. In case of these two components that would be:
hybridTheory
meteora
You may also want to pick custom name by passing name
parameter to @Component annotation, for example: @Component("myFavLinkinParkAlbum")
.
Then whenever you autowire CompactDisc
bean you can pass @Qualifier annotation and tell Spring which exactly bean you have in mind.
@Service
class Player {
private final CompactDisc compactDisc;
// note that starting from Spring 4.3
// you can omit @Autowired annotation from constructor
Player(@Qualifier("hybridTheory") CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
}
2. Mark one of your beans as @Primary
If in majority of injection points you inject always the same bean and the other one is used rarely you may consider marking the former with @Primary.
@Component
@Primary
public class HybridTheory implements CompactDisc { }
Whenever Spring finds out that there is more than one bean that matches injection point it will inject the one annotated with @Primary. Note that if there is more than one bean annotated with @Primary and implementing interface used in injection point the problem remains.
3. Use Java configuration
Although @Qualifier and @Primary solve the problem sometimes you may find that to learn which implementation is injected you need to travel a lot through source code. Sometimes the cleaner approach is to drop @Component (or other stereotype annotations) from your classes and declare beans using Java configuration:
public class HybridTheory implements CompactDisc { }
public class Meteora implements CompactDisc { }
@Configuration
class RecordsConfiguration {
@Bean
CompactDisc hybridTheory() {
return new HybridTheory();
}
@Bean
CompactDisc meteora() {
return new Meteora();
}
@Bean
Player player() {
return new Player(meteora());
}
}
Choose the one that fits your use case the best!
A simple and elegant approach is to use byType whenever possible. When that is not possible the byName approach can be used.
One way of using this is to name the beans manually - then the bean names will not differ after you change the classname. Note that naming the beans should only be used if you have multiple implementation of the same type, otherwise naming is not really needed. In the example below, the naming is part of the annotation e.g @Component("hybridTheory")
. This means that you can change the classname of HybridTheory
to whatever you want and the bean name will still be hybridTheory
.
When you inject something byName you can use the @Qualifier
annotation to specify which named bean you require. I personally prefer it to @Resource
since you can use @Qualifier
in constructors (I prefer constructor injection over setter- and field-based injection).
You should NOT inject the concrete types - that is what dependency injection is all about. Someone else (i.e. Spring) will handle the object creation for you ;)
public interface CompactDisc {
void play();
}
@Component
class CompactDiscPlayer {
@Autowired
CompactDiscPlayer(@Qualifier("hybridTheory") final CompactDisc compactDisc) {
// The bean of type HybridTheory will be used
compactDisc.play();
}
}
@Component("hybridTheory")
class HybridTheory implements CompactDisc {
public void play() {
System.out.println(getClass().getSimpleName());
}
}
@Component("meteora")
class Meteora implements CompactDisc {
public void play() {
System.out.println(getClass().getSimpleName());
}
}
The Spring docs describes the various options here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With