Spring doesn't create distinct beans when @Autowiring with different generic types

Viewed 567

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:

  • Two concrete types: Beer and Coffee
  • A Shop < T >
  • An Order < T >
  • A Manager class to @Autowire an Order < Beer > and an Order < Coffee >
  • (Then each Order < T > @Autowires a Shop < T >)
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
1 Answers

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.

Related