How to generate new managed bean programmatically in Spring

Hilikus :

This example is just a dummy example to show the problem I'm having so don't get too caught up in alternate ways to solve the concrete problem in here. My question is more about understanding the proper technique to solve a type of problem in Spring

Say I have a managed bean Info

@Component
public class Info {
  private final String activeProfile;
  private final Instant timestamp;

  public Info(@Value("${spring.profiles.active}") String activeProfile) {
    this.activeProfile = activeProfile;
    this.timestamp = Instant.now();
  }
}

The key here is that the bean needs something injected by Spring (the active profile in my example) and something that changes every time the bean is created (the timestamp in my example). Because of the latter, I can't use a Singleton scope. What is the proper way to get ahold of new instances of such a bean?

What I currently have is, the bean is not managed (no @Component, no @Value) and I have a managed service (a Controller) that invokes the constructor of a regular Info POJO explicitly. Something like

@RestController
public class InfoRestController { 
  @GetMapping
  public Info getInfo(@Value("${spring.profiles.active}") String activeProfile) {
    return new Info(activeProfile);
  } 
}

The problem with this solution is that it leaks the knowledge of the active profile to the controller just to pass it to the constructor of Info, when conceptually, the controller should not know about constructing the Info bean. That's one of the points of dependency injection

I have thought of some potential solutions:

  • Have a reference to an InfoFactory FactoryBean in the controller and then return factory.getObject();. But do I really need to create a new class for such a simple case?
  • Have a @Bean factory method that constructs the managed bean. This still has the problem that the method is instantiating the Info POJO explicitly, so it itself needs to have the Spring injection done to it. Also, this is complete boilerplate.

The construction of the Info bean is so trivial that I imagine there is a simpler way to accomplish this in Spring. Is there?

Hilikus :

The missing piece of the puzzle was javax.inject.Provider. I didn't know about it but it had exactly the interface that I was looking for. The final solution was to indeed let Spring manage the bean (Info) and use a Provider in the rest controller. This is the bean, almost exactly like in the OP

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Info {
  private final String activeProfile;
  private final Instant timestamp;

  public Info(@Value("${spring.profiles.active}") String activeProfile) {
    this.activeProfile = activeProfile;
    this.timestamp = Instant.now();
  }
}

The bean has the Prototype scope as suggested by ruhul. I had tried that before but without the Provider I was still stuck. This is how to return it in the controller

@RestController
public class InfoRestController { 
  @Autowire
  private Provider<Info> infoProvider;

  @GetMapping
  public Info getInfo() {
    return infoProvider.get();
  } 
}

For the sake of completeness, I found another, uglier way of doing it by injecting the spring ApplicationContext and then using context.getBean("info") but the dependency on the spring context and the string name were a smell. The solution with Provider is much more focused

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=322383&siteId=1