Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject multiple Spring beans, that inherit from shared interface, into an array within a service

I came across a situation that I had in another project that I'm not exactly sure the best way to do within Grails. To set it up, this is what I'm doing in a plain Spring project.

I have two classes that inherit from the same interface:

public interface BaseInterface {
    void doSomething();
}

public class Impl1 implements BaseInterface {
    public void doSomething(){
        System.out.println("doing impl 1");
    }
}

public class Impl2 implements BaseInterface {
    public void doSomething(){
        System.out.println("doing impl 2");
    }
}

So far pretty standard, I have N beans that I want to call sequentially to do work. (The example is obviously trivial). Within another Java class I can then do some magic to get all the beans injected(autowired) as an array.

@Autowired(required=false)
    private BaseInterface[] theWorkers;

This will give me an array of worker beans as long as I have added them to the bean container in the configuration.

Now I'm trying to do the same thing in Grails. The same formula doesn't work. Putting the @Autowired portion in a service, and creating Impl1 and Impl2 within resources.groovy does not seem to do the job. So I'm wondering what the best solution is:

1) I'm missing something simple that will make this work very easily.

2) Do something similar to what's suggested by duffymo here. I'd create a named bean in resources.groovy that used a custom factory. That factory would emit a class that would contain all the classes implementing a certain interface. I'd use something similar to the suggestion to pull the services/classes matching the criteria then have that service allow someone to iterate over it's subclasses to do work.

3) Create a named bean for each of the Impl# classes within resources.groovy and then just use their distinct names and inject all of them into the classes individually. This option would not really scale or give much dynamism, but would work.

like image 328
Scott Avatar asked Feb 22 '23 07:02

Scott


1 Answers

If you get access to the Spring application context you can call getBeansOfType which returns all known beans that implement a specified interface or extend a specified base class. So I'd register each bean in resources.groovy but also a manager class that gets a reference to the application context and finds the interface implementations for you. You said you want to call them sequentially, so you should implement the Ordered interface too.

Here's the manager class (put it in src/groovy/ in the correct folder for the package and rename it to whatever you want):

package com.foo

import org.springframework.beans.factory.InitializingBean
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

class BaseInterfaceManager implements ApplicationContextAware, InitializingBean {

   ApplicationContext applicationContext

   List<BaseInterface> orderedImpls

   void afterPropertiesSet() {
      orderedImpls = applicationContext.getBeansOfType(BaseInterface).values().sort { it.order }
   }
}

Then change the beans so they implement Ordered:

import org.springframework.core.Ordered;

public class Impl1 implements BaseInterface, Ordered {
   public void doSomething(){
      System.out.println("doing impl 1");
   }

   public int getOrder() {
      return 42;
   }
}

and

import org.springframework.core.Ordered;

public class Impl2 implements BaseInterface, Ordered {
   public void doSomething(){
      System.out.println("doing impl 2");
   }

   public int getOrder() {
      return 666;
   }
}

Register all three in resources.groovy (use whatever bean names you want):

beans = {

   impl1(Impl1)
   impl2(Impl2)

   baseInterfaceManager(BaseInterfaceManager)
}

And then you can add a dependency injection in a service or controller or whatever for the baseInterfaceManager bean and use it to loop through the implementation classes in order:

class FooService {

   def baseInterfaceManager

   void someMethod() {
      for (impl in baseInterfaceManager.orderedImpls) {
         impl.doSomething()
      }
   }
}
like image 130
Burt Beckwith Avatar answered May 10 '23 17:05

Burt Beckwith