Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 331
spl Avatar asked Oct 26 '22 21:10

spl


People also ask

Can we have two beans of same type in spring?

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.

How we can avoid bean ambiguity error in spring and possible ways?

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.

Can you Autowire byType when multiple beans?

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.

How does Spring know which bean to use?

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.


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.

like image 160
mentallurg Avatar answered Nov 09 '22 12:11

mentallurg