1

In Spring Boot 3 for jar-starters now we should use @Autoconfiguration. @Autoconfiguration is the @Configuration with proxyBeanMethods = false parameter.

In Spring Boot 2 i used the code like this. In singleton component i can create any count of prototype beans.

@Configuration
public class TestPrototypeConfiguration {

    @Bean
    public SingletonComponent singletoneComponent() {
        return new SingletonComponent(this::prototypeComponent);
    }

    @Bean
    @Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
    public PrototypeComponent prototypeComponent(int value) {
        return new PrototypeComponent(value);
    }

}

@RequiredArgsConstructor
public class PrototypeComponent {

    private final int value;
}

@RequiredArgsConstructor
public class SingletonComponent {

    private final IntFunction<PrototypeComponent> supplier;

    public void test() {
        for (int i = 0; i < 5; i++) {
            supplier.apply(i);
        }
    }
}

But for Spring boot 3 if I simple change @Configuration -> @Autoconfiguration the code will start work incorrect. Because when i call supplier the prototype bean will not construct as spring boot. It will be created as the simple java class.

Can you provide me any suggestion about how to solve this problem, please? Is it possible to solve this problem w/o ApplicationContext.getBean?

  • I think with minor changes and @Configuration itself you can make this work? Do you want to specifically use @Autoconfiguration? – Ashutosh Aug 29 '23 at 11:02
  • `proxyBeanMethods = false` means that your `prototypeComponent` `@Bean` method is now an unproxied factory bean that must return a complete `PrototypeComponent`. You cannot rely on Spring to inject dependencies into it because there is no method proxy in place to do it. You can add more bean parameters to your `@Bean` method and spring will resolve them, or you can add them as members to your `TestPrototypeConfiguration` class because it is itself a bean. – Andy Brown Aug 29 '23 at 13:13
  • I think that the spring cannot resolve arguments which i would like to pass from my code :) – Demid Demidov Aug 29 '23 at 13:33

2 Answers2

0

here's the modified code that should work in Spring Boot 3

   public class PrototypeComponent {
    private final int value;

    public PrototypeComponent(int value) {
        this.value = value;
    }

    // Other methods and properties
}


@Component
public class SingletonComponent {

    @Autowired
    private ObjectFactory<PrototypeComponent> prototypeComponentFactory;

    public void test() {
        for (int i = 0; i < 5; i++) {
            PrototypeComponent instance = prototypeComponentFactory.getObject(i); // Pass argument here
            // Use the instance as needed
        }
    }
}

In this example, I've used constructor injection to inject the prototype bean (PrototypeComponent) into the singleton bean (SingletonComponent). This way, each time you use the prototypeComponent field in the SingletonComponent class, you'll get a new instance of the prototype bean.

Please note that in Spring Boot 3, you don't need to manually define the bean creation logic using the supplier approach as in your initial code. Spring Boot's dependency injection mechanism will automatically take care of injecting the prototype bean when needed. This approach aligns with the principles of dependency injection and encapsulates the creation logic within the Spring framework.

Ashutosh
  • 917
  • 10
  • 19
  • This code is not work. At least `SingletonComponent` will create `PrototypeComponent` once when the singletone-bean will be constructed. Yes, we can change annotation in java-configuration for prototype bean for the following `@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)` But my question is how to create prototype-bean with arguments in constructor-stage? – Demid Demidov Aug 29 '23 at 12:10
  • Okay, in that case can you try creating an ObjectFactory (Some sort of factory design pattern) ? – Ashutosh Aug 29 '23 at 12:11
  • I meant Autowired private PrototypeComponent prototypeComponent; instead of this use Autowired private ObjectFactory prototypeComponentFactory; – Ashutosh Aug 29 '23 at 12:14
  • Yes, but this does not solve the problem for passing arguments to the prototype bean. – Demid Demidov Aug 29 '23 at 12:49
  • updated the answer, see if that make sense. – Ashutosh Aug 29 '23 at 12:56
  • By default, Spring doesn't provide a built-in mechanism to pass arguments to prototype beans through the ObjectFactory. If you encounter issues with the above approach, you might need to implement a custom scope. A custom scope allows you to control the creation of prototype beans and pass arguments. Let me know if you need any help there. – Ashutosh Aug 29 '23 at 12:57
  • Yes, I see that the ObjectFactory does not accept arguments. In my opinion very strange that in spring boot v3 the compatibility for creation of prototype bean with arguments is missed. – Demid Demidov Aug 29 '23 at 13:12
  • Not sure about it, being long time since i did this, Lets raise it to spring may be? – Ashutosh Aug 29 '23 at 13:13
  • I have found ObjectProvider which can solve this problem. But I don't know Is it the good practice for spring? – Demid Demidov Aug 31 '23 at 10:38
0

What I do, which works (i.e., this is in production and has been for a while and is fine on both Spring 5 and 6), is use an ObjectProvider.

@Component
public class SingletonComponent {
    @Autowired
    private ObjectProvider<PrototypeComponent> prototypeComponentFactory;

    public void test() {
        for (int i = 0; i < 5; i++) {
            var instance = prototypeComponentFactory.getObject(i);
            // Use the instance as needed
        }
    }
}

This will pass the argument through, assuming you have suitably annotated objects and constructors (i.e., there is a bean of type PrototypeComponent with scope PROTOTYPE in your context whose constructor takes an int or Integer).

I prefer to wrap it slightly, like this, so that I have more type safety:

@Component
public class SingletonComponent {
    @Autowired
    private ObjectProvider<PrototypeComponent> prototypeComponentFactory;

    private PrototypeComponent getComponent(int i) {
        return prototypeComponentFactory.getObject(i);
    }

    public void test() {
        for (int i = 0; i < 5; i++) {
            var instance = getComponent(i);
            // Use the instance as needed
        }
    }
}

You might delegate that wrapping to a bean, but that's not really necessary if you're only building instances for one place (which was true in my particular use-case).

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215