Search code examples
springprototypejavabeansfactoryautowired

Create a Spring Bean programmatically, but with Autowired within it


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().


Solution

  • 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");