Home > Software design >  Generic type reverse lookup with Spring Boot @Service autowirings
Generic type reverse lookup with Spring Boot @Service autowirings

Time:12-16

Spring Boot & Java 11 here. I have an abstract base class:

public abstract class AbstractBurninator {
  // ...
}

And some subclasses, such as:

public class FizzBurninator extends AbstractBurninator {}
public class BuzzBurninator extends AbstractBurninator {}
public class FoobazBurninator extends AbstractBurninator {}

But there are many more subclasses of it besides those three. I also have an interface:

public interface DoesThings<B extends AbstractBurninator> {
    void doAllTheThings(B burninator, String payload);
}

So each implementation of the interface must specify the AbstractBurninator subclass it operates on. Hence I have:

  • public class DoesFizzThings implements DoesThings<FizzBurninator> {}
  • public class DoesBuzzThings implements DoesThings<BuzzBurninator> {}
  • public class DoesFoobazThings imlpements DoesThings<FoobazBurninator> {}
  • etc.

I now have a Spring Boot service (annotated with @Service) that gets autowired with a list of all List<DoesThings>. Inside that service I have a method that will infer (from certain logic) and instantiate an AbstractBurninator subclass, and it then needs to look up the DoesThings implementation associated with it. Hence if it infers an instance of FizzBurninator, I want it to select the DoesFizzThings instance from the autowired list:

@Service
public class BurninationService {

    @Autowired
    private List<DoesThings> thingDoers;

    public void hahaha(Whistlefeather wf) {

      // use 'wf' and other stateful data to infer a subclassed instance of 'AbstractBurninator':
      AbstractBurninator burninator = inferSomehow();

      // TODO: how to figure out which item of 'thingDoers' matches 'burninator'?
    
    }

What's an easy and elegant way of doing this TBD lookup? I could inject a map instead:

private Map<AbstractBurninator,DoesThings> thingDoers;

But that seems unnecessary since each DoesThing has 1-and-only-1 corresponding AbstractBurninator. Any ideas? It's possible this can be done with straight Java generics, but I'm guessing Spring has some nifty utility that can help here.

CodePudding user response:

If you are comfortable with wiring your Spring context into your service, you could do something like this (inspired by this SO accepted answer)

   private <T extends AbstractBurninator> DoesThings<T> getSomeBurn(Class<T> clazz) {
      String[] arr = ctx.getBeanNamesForType(ResolvableType.forClassWithGenerics(DoesThings.class, clazz));
      if (arr.length == 1) {
         return (DoesThings<T>) ctx.getBean(arr[0]);
      } else {
         throw new IllegalArgumentException("No burninator found");
      }
   }

This comes with a beautiful "unchecked cast" warning. Also, in my experience, wiring the application context indicates a design problem and definitely complicates testing.

  • Related