I'm trying to figure out the neatest and working way to use multiple copies of a component in Spring 5 differentiated by the generic type. Clearly I'm missing a concept, because based on what I read I feel this (below) should work and it doesn't, so I'm probably misunderstanding how generic types work as qualifiers.
I have this arrangement:
public class Beer {}
public class Coffee {}
@Component
public class Order<T> {
@Autowired
Shop<T> shopToSendOrderTo;
void place(int quantity) {
log.info("Inside the order: " + this);
shopToSendOrderTo.sendOrder(quantity);
}
}
@Component
public class Shop<T> {
void sendOrder(int quantity) {
log.info("Inside the shop: " + this);
}
}
@Configuration
public class Manager implements InitializingBean {
@Autowired
Order<Beer> beerOrder;
@Autowired
Order<Coffee> coffeeOrder;
@Override
public void afterPropertiesSet() {
beerOrder.place(2);
coffeeOrder.place(0);
}
}
However I end up creating only one shop bean and one order bean when I wanted a shop < Beer > and a shop < Coffee > and an order < Beer > and an order < Coffee >
I can see this clearly from the actuator/beans output:
"shop":{
"aliases":[
],
"scope":"singleton",
"type":"com.example.generics.Generics.Shop",
"resource":"file [...]",
"dependencies":[
]
},
"order":{
"aliases":[
],
"scope":"singleton",
"type":"com.example.generics.Generics.Order",
"resource":"...",
"dependencies":[
"shop"
]
}
And you can see there is only one of each when you print the this reference:
2020-06-15 00:40:05.600 INFO 849 --- [ restartedMain] c.e.g.Generics.Order : Inside the order: com.example.generics.Generics.Order@70792f0a
2020-06-15 00:40:05.602 INFO 849 --- [ restartedMain] c.e.g.Generics.Shop : Inside the shop: com.example.generics.Generics.Shop@41e7f2d5
2020-06-15 00:40:05.602 INFO 849 --- [ restartedMain] c.e.g.Generics.Order : Inside the order: com.example.generics.Generics.Order@70792f0a
2020-06-15 00:40:05.602 INFO 849 --- [ restartedMain] c.e.g.Generics.Shop : Inside the shop: com.example.generics.Generics.Shop@41e7f2d5
The default autowiring is by type, not by name, so when there is more than one bean of the same type, you have to use the @Qualifier annotation.
When you have such cases when there are multiple beans, you can resolve ambiguity by designating one of the bean as primary bean using annotation @Primary . Spring will automatically choose which you have annotated as @Primary in case of ambiguity.
autowire byType - For this type of autowiring, class type is used. So there should be only one bean configured for this type in the spring bean configuration file. autowire by constructor - This is almost similar to autowire byType, the only difference is that constructor is used to inject the dependency.
The identifier of the parameter matches the name of one of the beans from the context (which is the same as the name of the method annotated with @Bean that returns its value). In this case, Spring chooses that bean for which the name is the same as the parameter.
It is important to know, that @Component
means a singleton. This means, that a bean of this type will be created only once and on all places will be injected the same instance.
If these classes haven't used generics, it would be easy to understand what you see.
But the information about generic parameters is available only at teh compile time. In the runtime it is not available. That's why at the runtime Order<Beer>
and Order<Coffee>
mean effectively the same, Order<Object>
. That's why beerOrder
and coffeeOrder
have both the same value. And in actuator we see that there is one bean of type Order
, which is correct. The same holds for Shop<T>
: No matter how many references you have, all of them mean in the runtime the same, Shop<Object>
.
Different solutions are possible to get separate beans.
1) Use prototype scope
@Component
@Scope("prototype")
public class Order<T> {
...
@Component
@Scope("prototype")
public class Shop<T> {
...
Then on every injection place a new instance will be created. As a consequence, beerOrder
and coffeeOrder
will be different bean instances. Correspondingly, each instance of Order
will have its own instance of Shop
.
But: In case you have somewhere in the code other injected beans of type Order<Beer>
, each of them will be a separate instance. You decide, if this is what you want.
2) Define separate classes
If you want that Order<Beer>
in all places in your code injects the same bean instance (singleton), and Order<Coffee>
also in all places injects the same bean instance (another singleton), but different from places with Order<Coffee>
, then a possible solution is to define corresponding classes explicitly.
As a consequence you may want to define an abstract class that has a field of type Shop<T>
. Each Order
class will have to provide its own copy of Shop
to the parent constructor. The code may look as follows:
public abstract class AbstractOrder extends Order<T> {
private Shop<T> shop;
protected AbstractOrder(Shop<T> shop) {
this.shop = shop;
}
@Component
public class BeerOrder extends Order<Beer> {
...
public BeerOrder(BeerShop shop) {
super(shop);
}
@Component
public class CoffeeOrder extends Order<Coffee> {
...
public CoffeeOrder(CoffeeShop shop) {
super(shop);
}
In such case Spring will create exactly one bean of each type. Where needed, Spring will pass proper beans to constructors.
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