0

How do I instantiate a Spring Bean that has some @Autowired beans within it? The caveat is that the instance bean type must be discovered dynamically at runtime (whereas the @Autowired bean can still be singleton).

Example:

Interface

public interface Client {
  public void process(String s);
}

ClientA.class

@Component
@Scope("prototype")
public class ClientA implements Client {
  @Autowired
  MyHelperService svc;

  public void process(String s) {...}
}

ClientB.class

@Component
@Scope("prototype")
public class ClientB implements Client {
  @Autowired
  MyHelperService svc;

  public void process(String s) {...}
}

ClientFactory.class

@Component
public class ClientFactory {
  public Client createClient(String type) {
    .. create an instance of ClientA or ClientB ..
  }
}

MyService.class

@Service
public class MyService {
  @Autowired
  ClientFactory factory;

  Client client;

  public void processList(List<String> requests) {
    for(String s: requests) {
      client = factory.createClient(s);
      client.process(s);
    }
  }
}

Though this sample code is for illustration purposes here, the pattern applies to our needs. More specifically, we are multi-threading our app by creating Callable<?> tasks and submitting them to an ExecutorService with parallel threads (and it's those tasks which each need their own instance of Client whose lifespan should end after we call it's process method).

The client instances use a singleton shared MyHelperService service. Since that should be @Autowired, our factory can't simply construct the clients like new ClientA() or new ClientB().

Michael C
  • 53
  • 6

1 Answers1

1

So you should know that adding a @Bean annotation on method, does not create a bean it just creates a bean definition, and only after calling this method spring will create a bean for you.

You can use applicationContext to get bean defined with prototype scope wherever you want in your application and your ClientFactory is a good place to do that

public class ClientFactory {

    private ApplicationContext applicationContext;

    public ClientFactory(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public Client createInstance(String request) {
        if (request.equals("A")) {
            return applicationContext.getBean("clientA", Client.class);
        }
        return applicationContext.getBean("clientB", Client.class);
    }
} 

And your bean configuration will look like this

@Configuration
public class AppConfiguration {

    @Bean
    @Scope("prototype")
    public Client clientA(){
        return new ClientA();
    }

    @Bean
    @Scope("prototype")
    public Client clientB(){
        return new ClientB();
    }

    @Bean
    public ClientFactory clientFactory(ApplicationContext applicationContext){
        return new ClientFactory(applicationContext);
    }
}

Then you just need to inject your ClientFactory and create instances of the Client beans with clientFactory.createInstance("A");

szymon_prz
  • 540
  • 3
  • 14
  • @syzmon_prz When I call `applicationContext.getBean(Client.class, "A")` I get `NoUniqueBeanDefinitionException` stating that expected a single match but found `clientA` and `clientB`. Also, the debug breakpoint I set in `ClientFactory.createInstance` is never hit. How do I confirm my `@Configuration` was loaded and how does Spring know to call that `createInstance` method? – Michael C Jan 17 '23 at 23:04
  • @syzmon_prz Nevermind. I made sure to give my `@Bean` a name and then to use that same name in the `applicationContext.getBean` method. So Spring is marrying them up now and I'm successfully getting my instance beans. Thank you! – Michael C Jan 17 '23 at 23:33
  • Don't. Do it the other way around. So you do't have to work with the context in your application. Make the factory and the beans Spring managed and move the call to `getBean` to your factory. That way you don't need the getbean call in other methods and your own code can be clean. The factory can be a singleton that way. – M. Deinum Jan 18 '23 at 07:41
  • @M.Deinum really nice! Will edit my answer with your method – szymon_prz Jan 18 '23 at 07:54