3.7 Bean Factories

In many cases, you may want to make available as a bean a class that is not part of your codebase such as those provided by third-party libraries. In this case, you cannot annotate the already compiled class. Instead, you should implement a @Factory.

A factory is a class annotated with the Factory annotation that provides 1 or more methods annotated with a bean scope annotation. Which annotation you use depends on what scope you want the bean to be in. See the section on bean scopes for more information.

The return types of methods annotated with a bean scope annotation are the bean types. This is best illustrated by an example:

  1. @Singleton
  2. class CrankShaft {
  3. }
  4. class V8Engine implements Engine {
  5. private final int cylinders = 8;
  6. private final CrankShaft crankShaft;
  7. public V8Engine(CrankShaft crankShaft) {
  8. this.crankShaft = crankShaft;
  9. }
  10. public String start() {
  11. return "Starting V8";
  12. }
  13. }
  14. @Factory
  15. class EngineFactory {
  16. @Singleton
  17. Engine v8Engine(CrankShaft crankShaft) {
  18. return new V8Engine(crankShaft);
  19. }
  20. }
  1. @Singleton
  2. class CrankShaft {
  3. }
  4. class V8Engine implements Engine {
  5. final int cylinders = 8
  6. final CrankShaft crankShaft
  7. V8Engine(CrankShaft crankShaft) {
  8. this.crankShaft = crankShaft
  9. }
  10. String start() {
  11. "Starting V8"
  12. }
  13. }
  14. @Factory
  15. class EngineFactory {
  16. @Singleton
  17. Engine v8Engine(CrankShaft crankShaft) {
  18. new V8Engine(crankShaft)
  19. }
  20. }
  1. @Singleton
  2. internal class CrankShaft
  3. internal class V8Engine(private val crankShaft: CrankShaft) : Engine {
  4. private val cylinders = 8
  5. override fun start(): String {
  6. return "Starting V8"
  7. }
  8. }
  9. @Factory
  10. internal class EngineFactory {
  11. @Singleton
  12. fun v8Engine(crankShaft: CrankShaft): Engine {
  13. return V8Engine(crankShaft)
  14. }
  15. }

In this case, the V8Engine is built by the EngineFactory class’ v8Engine method. Note that you can inject parameters into the method and these parameters will be resolved as beans. The resulting V8Engine bean will be a singleton.

A factory can have multiple methods annotated with bean scope annotations, each one returning a distinct bean type.

If you take this approach, then you should not invoke other bean methods internally within the class. Instead, inject the types via parameters.
To allow the resulting bean to participate in the application context shutdown process, annotate the method with @Bean and set the preDestroy argument to the name of the method that should be called to close the bean.

Nullability

Factory methods can return null to allow for beans to be created conditionally. The use of @Requires should always be the preferred method to conditionally create beans and returning null from a factory method should only be done if using @Requires is not possible.

For example:

  1. public interface Engine {
  2. Integer getCylinders();
  3. }
  4. @EachProperty("engines")
  5. public class EngineConfiguration implements Toggleable {
  6. private boolean enabled = true;
  7. private Integer cylinders;
  8. @NotNull
  9. public Integer getCylinders() {
  10. return cylinders;
  11. }
  12. public void setCylinders(Integer cylinders) {
  13. this.cylinders = cylinders;
  14. }
  15. @Override
  16. public boolean isEnabled() {
  17. return enabled;
  18. }
  19. public void setEnabled(boolean enabled) {
  20. this.enabled = enabled;
  21. }
  22. }
  23. @Factory
  24. public class EngineFactory {
  25. @EachBean(EngineConfiguration.class)
  26. public Engine buildEngine(EngineConfiguration engineConfiguration) {
  27. if (engineConfiguration.isEnabled()) {
  28. return engineConfiguration::getCylinders;
  29. } else {
  30. return null;
  31. }
  32. }
  33. }
  1. interface Engine {
  2. Integer getCylinders()
  3. }
  4. @EachProperty("engines")
  5. class EngineConfiguration implements Toggleable {
  6. boolean enabled = true
  7. @NotNull
  8. Integer cylinders
  9. }
  10. @Factory
  11. class EngineFactory {
  12. @EachBean(EngineConfiguration)
  13. Engine buildEngine(EngineConfiguration engineConfiguration) {
  14. if (engineConfiguration.enabled) {
  15. (Engine){ -> engineConfiguration.cylinders }
  16. } else {
  17. null
  18. }
  19. }
  20. }
  1. interface Engine {
  2. fun getCylinders(): Int
  3. }
  4. @EachProperty("engines")
  5. class EngineConfiguration : Toggleable {
  6. val enabled = true
  7. @NotNull
  8. val cylinders: Int? = null
  9. override fun isEnabled(): Boolean {
  10. return enabled
  11. }
  12. }
  13. @Factory
  14. class EngineFactory {
  15. @EachBean(EngineConfiguration::class)
  16. fun buildEngine(engineConfiguration: EngineConfiguration): Engine? {
  17. return if (engineConfiguration.isEnabled) {
  18. object : Engine {
  19. override fun getCylinders(): Int {
  20. return engineConfiguration.cylinders!!
  21. }
  22. }
  23. } else {
  24. null
  25. }
  26. }
  27. }