7

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;
  }
}
Ram
  • 2,237
  • 4
  • 23
  • 27
  • 1
    I assume you are receiving downvotes because the question about "lesser evil" is entirely subjective. Perhaps rephrase the question in terms of immutability and whatever else you desire to achieve. – jaco0646 Jan 25 '19 at 23:59
  • I agree with @jaco0646. The way the question is phrased invites opinions, but could perfectly well be answered objectively. It's a good question that needs tweaking. – Don Branson Jan 26 '19 at 00:14
  • 1
    You may find your answer in [Spring bean with runtime constructor arguments](https://stackoverflow.com/q/35108778/1371329). – jaco0646 Jan 26 '19 at 00:49
  • Thanks for your feedback and I have reworded my question. – Ram Jan 30 '19 at 00:34

1 Answers1

11

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

Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • 1
    Note in my comment to the OP that Spring already implements this factory pattern via its `BeanFactory` interface. Advantages of rolling your own factory include type safety around the runtime argument(s) and elimination of a Spring dependency in the `Consumer`. It just costs you an extra class. – jaco0646 Jan 26 '19 at 17:51
  • 1
    Is this a working example? Please update the void in factory class. Also, how are you able to read String requestData in factory class? It shows error "Could not Autowire" for me. – Black Diamond Oct 17 '19 at 20:34
  • good catch . let try to use `@Bean(autowireCandidate = false)` when defining the prototype bean. – Ken Chan Oct 18 '19 at 19:21
  • 1
    Hello @ken , i used autowireCandidate = false and i still get the "required a bean of type 'java.lang.String' that could not be found" error. do you know why? – Hussain Apr 21 '21 at 07:24
  • Hello @AdiV did you find the solution for this problem? – Hussain Apr 21 '21 at 07:36
  • 1
    Why do you need @Bean(autowireCandidate = false) and @Scope("prototype") on "createProcessor", if you anyway call it like "processorFactory.createProcessor". Both annotations on this method do not make sense to me. – Igor Bljahhin May 05 '23 at 12:06