6

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
5
  • 1
    The behavior is complicated and probably a bug, but if you want separate beans, the most direct way to do so is to use @Bean methods. Commented Jun 15, 2020 at 1:09
  • You must create them yourself. Beans aren't created based on the info in @Autowired but either based on the @Bean information or detected through @Component. It won't automagically create an instance based on the generic type you want (unless you start to extend the framework for that purpose). Create a configuration class that constructs both instances and remove @Component. Commented Jun 15, 2020 at 5:43
  • @M.Deinum if I take your advice and use an @Bean annotation to create a bean for CoffeeOrder and BeerOrder, inside those bean methods I have to instantiate the object using new, so the Shop inside each does not get autowired (?) How would you deal with that? Commented Jun 15, 2020 at 22:09
  • It is still a bean and spring will autowire as well. The @Bean is the same as <bean> in xml and basically there is no difference how a bean comes into existence. Unless you aren't exposing the Order<Coffee> etc. as beans but just instantiate them. You have to make sure that also a Shop<Coffee> is available as a bean. Commented Jun 16, 2020 at 5:22
  • Thank you, I didn't get that the @Bean still gets autowired. I tried making concrete types for each of my generics: BeerShop extends Shop<Beer> etc but reverted to the idea about using @Bean. Naturally the codebase I'm working on is more complex than this example and it helped a lot. Still though Spring won't wire through the @Autowired properties in those @Beans where they have generic type <T>, but still it is much neater. Thanks. Commented Jun 16, 2020 at 22:08

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Hello. 4 years later, who know if this notification will find you. In springboot it works with @Scope("prototype") but not with @Scope("request"). Why it is like this?
What do you mean by "but not with..."? Better: Create a new question. Describe what exactly you need, what have you done so far.
@Qualifier is another possible approach, probably the quickest one if you already have the OP's code.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.