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;
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With