Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Spring Prototype Beans with constructor arguments? [closed]

I do use Spring and Lombok.
Without prototype beans, we have to pass around dependencies which the target class requires.
How do we mark a bean as prototype and correctly handle dependent beans and constructor arguments?

Option 1 - No prototype beans

@Component @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Consumer {
  private final SomeDependency iDontNeed; // Consumer class doesn't need
  private final SomeDependency2 iDontNeed2;

  public void method() {
    new Processor("some random per request data", iDontNeed, iDontNeed2);
  }
....
@Value @RequiredArgsConstructor
public class Processor {
  private final String perRequestInputData;
  private final SomeDependency iReallyNeed;
  private final SomeDependency2 iReallyNeed2;
}

Option 2 - Prototype beans

@Component @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Consumer {
  private final Provider<Processor> processorProvider;

  public void method() {
    Processor p = processorProvider.get();
    p.initializeWith("some random per request data");
  }
....
@Component @Scope("prototype") 
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Processor {
  private final SomeDependency iReallyNeed;
  private final SomeDependency2 iReallyNeed2;

  private String perRequestInputData; //wish I was final
  private boolean initialized; //wish I was not needed

  public void initializeWith(String perRequestInputData) {
    Preconditions.checkState(!initialized);
    this.perRequestInputData = perRequestInputData
    initialized = true;
  }
}
like image 224
Ram Avatar asked Jul 24 '26 10:07

Ram


1 Answers

My comments:

Option 1:

It is clean and simple. Not every class is required to be declared as a Spring bean. If a class is simple enough that does not use any Spring features (such as @Cache , @Tranascational etc.) , it is okay to KISS and not declaring it as a Spring bean and create it manually. The Consumer just like the Main class to drive the logic ,so I would say SomeDependency is also required by Consumer in some sense as it needs them to drive the logic of creating the Processor.

Option 2:

Agree with you. Not so nice as we need separate call and the extra "initialized" property to make sure Processor are created properly when compared with the option 1 which we simply need to create it through constructor. But Processor is a Spring bean so that we can apply Spring feature to it very easily.

Do we have alternatives?

My alternative is to use the factory pattern with @Configuration and @Bean to have the best of both worlds:

First define a factory:

@Configuration
public class ProcessorFactory {

     @Autowired
     private final SomeDependency dep1; 

     @Autowired
     private final SomeDependency2 dep2;

     @Bean(autowireCandidate = false)
     @Scope("prototype") 
     public Processor createProcessor(String requestData) {
        return new Processor(requestData, dep1, dep2);
    }
}

Then in the consumer :

@Component
public class Consumer {

     @Autowired 
     private final ProcessorFactory processorFactory;


      public void method() {
        Processor p = processorFactory.createProcessor("some random per request data");
        p.blablbaba();
      }
}

Note: @Bean(autowireCandidate = false) on Processor @Bean is necessary. Otherwise , Spring will try to find a bean with the String type to create a Processor during start up. Since there is no bean with the String type , it will throw exception. Setting autowireCandidate to false can disable Spring create it. After all , we will create it by manually from ProcessorFactory

like image 94
Ken Chan Avatar answered Jul 28 '26 15:07

Ken Chan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!