How can I create and autowire beans based on the Enum type?

advertisements

Apologies if my terminology is jumbled up, I'm rather new to Spring.

I have inherited an application that has a rather unappealing approach as follows:

  • The app deals with 3 types of data (T1, T2, T3). The types are in Enum class DataType.

  • There is a data service class hierarchy, with abstract DataService class, and 3 derived classes (one per DataType) T1DataService, T2DataService ....

  • The config creates 3 beans for the Data Service:

    @Bean(name = "T1DataService")
    public DataService getDataService() throws Exception {
        return new T1DataService();
    }
    
    @Bean(name = "T2DataService")
    public DataService getDataService() throws Exception {
        return new T2DataService();
    }
    
    @Bean(name = "T3DataService")
    public DataService getDataService() throws Exception {
        return new T3DataService();
    }
    
    
  • Similarly, there is a compute service, with only 1 class, but 3 bean instances, where the data type is passed to each instance's constructor:

    @Bean(name = "T1ComputeService")
    public ComputeService getComputeService() throws Exception {
        return new ComputeService (T1);
    }
    
    @Bean(name = "T2ComputeService")
    public ComputeService getComputeService() throws Exception {
        return new ComputeService (T2);
    }
    
    @Bean(name = "T3ComputeService")
    public ComputeService getComputeService() throws Exception {
        return new ComputeService (T3);
    }
    
    
  • There is a request processor class, which needs data from the DataService, as well as computations from ComputeService, based on the data type specified in the request.

    The way it was coded, was to Autowire all 3 data and 3 compute service beans:

    @Autowired @Qualifier("T1DataService")
    protected DataService T1DataService;
    
    @Autowired @Qualifier("T2DataService")
    protected DataService T2DataService;
    
    @Autowired @Qualifier("T3DataService")
    protected DataService T3DataService;
    
    @Autowired @Qualifier("T1ComputeService")
    protected ComputeService T1ComputeService;
    
    @Autowired @Qualifier("T2ComputeService")
    protected ComputeService T2ComputeService;
    
    @Autowired @Qualifier("T3ComputeService")
    protected ComputeService T3ComputeService;
    
    

    And then, in the code of the processor class:

    DataService dataService = null;
    ComputeService computeService = null;
    
    switch (request.dataType) {
    case T1:
        dataService = T1DataService;
        computeService = T1ComputeService;
        break;
    case T2:
        dataService = T2DataService;
        computeService = T2ComputeService;
        break;
    case T3:
        dataService = T3DataService;
        computeService = T3ComputeService;
        break;
    
    

This seems like a REALLY REALLY bad way of picking the correct object.

If these weren't autowired Spring beans, but regular POJOs, I would have simply stashed them in a HashMap

static HashMap<DataType, DataService> dataServices = new HashMap<>();
dataServices.put(T1, new T1DataService());
dataServices.put(T2, new T2DataService());
dataServices.put(T3, new T3DataService());

static HashMap<DataType, ComputeService> computeServices = new HashMap<>();
for (DataType dataType : DataType.allDataTypes()) {
    computeServices.put(dataType , new ComputeService(dataType));
}

... and then in the processor class, access the correct service object by getting it from HashMap by a key:

DataService dataService = dataServices.get(request.dataType);
ComputeService computeService = computeServices.get(request.dataType);

Is there any way I can achieve a code similarly clean and concise using Spring?


I would put the bean name or class in the enum and use ApplicationContext#getBean (with lombok annotations):

@RequiredArgsConstructor
enum DataType {
   T1("T1DataService", "T1ComputeService"),
   T2("T2DataService", "T2ComputeService"),
   T3("T3DataService", "T3ComputeService");

   @Getter
   private final String dataServiceName;

   @Getter
   private final String computeServiceName;
}

then

DataService dataService = applicationContext.getBean(DataType.T1.getDataServiceName());
ComputeService computeService = applicationContext.getBean(DataType.T1.getComputeServiceName());

(something like that, note that you could also assume the bean name is normalized and do a DataType.T1.name() + "DataService")