A factory is a class annotated with the 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:
class CrankShaft {
}
class V8Engine implements Engine {
final int cylinders = 8
final CrankShaft crankShaft
V8Engine(CrankShaft crankShaft) {
this.crankShaft = crankShaft
}
@Override
String start() {
"Starting V8"
}
}
@Factory
class EngineFactory {
@Singleton
Engine v8Engine(CrankShaft crankShaft) {
new V8Engine(crankShaft)
}
}
@Singleton
internal class CrankShaft
internal class V8Engine(private val crankShaft: CrankShaft) : Engine {
private val cylinders = 8
override fun start(): String {
return "Starting V8"
}
}
@Factory
internal class EngineFactory {
@Singleton
fun v8Engine(crankShaft: CrankShaft): Engine {
return V8Engine(crankShaft)
}
}
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.
Factory methods can return throw to allow for beans to be disabled conditionally. The use of @Requires should always be the preferred method to conditionally create beans and throwing an exception a factory method should only be done if using is not possible.
For example:
public interface Engine {
Integer getCylinders();
}
@EachProperty("engines")
public class EngineConfiguration implements Toggleable {
private boolean enabled = true;
private Integer cylinders;
@NotNull
public Integer getCylinders() {
return cylinders;
}
public void setCylinders(Integer cylinders) {
this.cylinders = cylinders;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
@Factory
public class EngineFactory {
@EachBean(EngineConfiguration.class)
public Engine buildEngine(EngineConfiguration engineConfiguration) {
if (engineConfiguration.isEnabled()) {
return engineConfiguration::getCylinders;
} else {
throw new DisabledBeanException("Engine configuration disabled");
}
}
}
interface Engine {
Integer getCylinders()
}
@EachProperty("engines")
class EngineConfiguration implements Toggleable {
boolean enabled = true
@NotNull
Integer cylinders
}
@Factory
class EngineFactory {
@EachBean(EngineConfiguration)
Engine buildEngine(EngineConfiguration engineConfiguration) {
if (engineConfiguration.enabled) {
(Engine){ -> engineConfiguration.cylinders }
} else {
throw new DisabledBeanException("Engine configuration disabled")
}
}
}
Injection Point
A common use case with factories is to take advantage of annotation metadata from the point at which an object is injected such that behaviour can be modified based on said metadata.
@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Cylinders {
int value() default 8;
}
@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
@interface Cylinders {
int value() default 8
}
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Cylinders(val value: Int = 8)
The above annotation could be used to customize the type of engine we want to inject into a vehicle at the point at which the injection point is defined:
@Singleton
private final Engine engine;
Vehicle(@Cylinders(6) Engine engine) {
this.engine = engine;
String start() {
return engine.start();
}
}
@Singleton
internal class Vehicle(@param:Cylinders(6) private val engine: Engine) {
fun start(): String {
return engine.start()
}
}
The above Vehicle
class specifies an annotation value of @Cylinders(6)
indicating an Engine
of six cylinders is needed.
To implement this use case you can define a factory that accepts the instance which can be used to analyze the annotation values defined:
@Factory
class EngineFactory {
@Prototype
Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { (1)
final int cylinders = injectionPoint
.getAnnotationMetadata()
.intValue(Cylinders.class).orElse(8); (2)
switch (cylinders) { (3)
case 6:
return new V6Engine(crankShaft);
case 8:
return new V8Engine(crankShaft);
default:
throw new IllegalArgumentException("Unsupported number of cylinders specified: " + cylinders);
}
}
}
@Factory
class EngineFactory {
@Prototype
Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { (1)
final int cylinders = injectionPoint
.getAnnotationMetadata()
.intValue(Cylinders.class).orElse(8) (2)
switch (cylinders) { (3)
case 6:
return new V6Engine(crankShaft)
case 8:
return new V8Engine(crankShaft)
default:
throw new IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
}
}
}
@Factory
internal class EngineFactory {
@Prototype
fun v8Engine(injectionPoint: InjectionPoint<*>, crankShaft: CrankShaft): Engine { (1)
val cylinders = injectionPoint
.annotationMetadata
.intValue(Cylinders::class.java).orElse(8) (2)
return when (cylinders) { (3)
6 -> V6Engine(crankShaft)
8 -> V8Engine(crankShaft)
else -> throw IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
}
}