Factory patterns are often used in software projects to encapsulate the object creation from the client. In my previous developer career, I have seen many factory pattern implementations. Most of them were “Simple Factories” (not Factory Method, not Abstract Factory) without reflection, based on a simple creation parameter.
In the following article I try to port the simple factory pattern from javase to the CDI world. My factory implementation will be based on the concept of Qualifier and AnnotationLiteral.
🐦 A simple non-CDI bird factory implementation
Here is an example of a simple bird factory, which can create 3 types of birds:
- eagles
- blackbirds
- sparrows
The client creates new birds based on dynamic data from System.in
. The factory is based on qualifiers (E=Eagle, B=Blackbird, …) and doesn’t use reflection. If there is a new bird type, the factory has to be changed to add the new bird qualifier.
The products:
public interface Bird {
public void tweet();
}
public class Eagle implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm an eagle!");
}
}
public class Blackbird implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm a blackbird!");
}
}
public class Sparrow implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm a sparrow!");
}
}
The simple factory:
public class BirdFactory {
public static Bird createBird(String _birdType) {
if ("S".equals(_birdType)) {
return new Sparrow();
} else if ("B".equals(_birdType)) {
return new Blackbird();
} else if ("E".equals(_birdType)) {
return new Eagle();
} else {
throw new IllegalArgumentException("bird type " + _birdType + " not supported");
}
}
}
The client:
public class Client {
public void doSomeStuff(){
try (Scanner scanner = new Scanner(System.in)) {
System.out.println("Which bird should tweet?");
while (scanner.hasNextLine()) {
String birdType = scanner.nextLine();
if (birdType.equalsIgnoreCase("q")) {
break;
}
Bird bird = BirdFactory.createBird(birdType);
bird.tweet();
}
}
}
}
Test run:
public class Main {
public static void main(String[] args) {
Client client = new Client();
client.doSomeStuff();
}
}
Here is the output, when you run the program with the input: S, B, B, E, q
Which bird should tweet?
Input: S
Tweet, Tweet, I'm a sparrow!
Input: B
Tweet, Tweet, I'm a blackbird!
Input: B
Tweet, Tweet, I'm a blackbird!
Input: E
Tweet, Tweet, I'm an eagle!
Input: q
🐦 A CDI bird factory ≪AnnotationLiteral≫ implementation
The products:
First you have to create a Qualifier for the different bird implementations.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
public @interface BirdType {
String value();
}
Then all the different bird implementations have to be annotated with the newly created Qualifier.
public interface Bird {
public void tweet();
}
@BirdType("B")
public class Blackbird implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm a blackbird!");
}
}
@BirdType("E")
public class Eagle implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm an eagle!");
}
}
@BirdType("S")
public class Sparrow implements Bird {
@Override
public void tweet() {
System.out.println("Tweet, Tweet, I'm a sparrow!");
}
}
The CDI simple factory:
JEE javadoc: “AnnotationLiteral supports inline instantiation of annotation type instances.”
To create a factory, which can instantiate bird objects based on the newly created Annotation BirdType
, we have to use AnnotationLiteral
`.
public class BirdTypeNameLiteral extends AnnotationLiteral<BirdType> implements BirdType {
private final String name;
public BirdTypeNameLiteral(String _name) {
this.name = _name;
}
@Override
public String value() {
return this.name;
}
}
Now let’s create the corresponding factory.
@ApplicationScoped
public class BirdFactory {
@Inject
@Any
private Instance<Bird> birdInstance;
public Bird createBird(String _birdType) {
Instance<Bird> instance = this.birdInstance.select(new BirdTypeNameLiteral(_birdType));
if (!instance.isResolvable()) {
throw new IllegalArgumentException("bird type " + _birdType + " not supported");
}
return instance.get();
}
}
The client:
public class ConsoleClient {
@Inject
private Logger logger;
@Inject
private BirdFactory birdFactory;
public void doSomeStuff() {
try (Scanner scanner = new Scanner(System.in)) {
logger.info("Which bird should tweet?");
while (scanner.hasNextLine()) {
String birdType = scanner.nextLine();
if (birdType.equalsIgnoreCase("q")) {
break;
}
Bird bird = birdFactory.createBird(birdType);
bird.tweet();
}
}
}
}
Test run:
public class Main {
public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
ConsoleClient client = container.select(ConsoleClient.class).get();
client.doSomeStuff();
}
}
}
The output
$ gradle run
> Task :run
Aug 14, 2018 2:07:25 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 3.0.5 (Final)
Aug 14, 2018 2:07:26 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Aug 14, 2018 2:07:26 PM org.jboss.weld.environment.se.WeldContainer fireContainerInitializedEvent
INFO: WELD-ENV-002003: Weld SE container 99aa398e-5705-43df-bd74-714fae6dc440 initialized
[main] INFO ConsoleClient - Which bird should tweet?
[main] INFO Sparrow - Tweet, Tweet, I'm a sparrow!
[main] INFO Blackbird - Tweet, Tweet, I'm a blackbird!
[main] INFO Blackbird - Tweet, Tweet, I'm a blackbird!
[main] INFO Eagle - Tweet, Tweet, I'm an eagle!
Aug 14, 2018 2:07:38 PM org.jboss.weld.environment.se.WeldContainer shutdown
INFO: WELD-ENV-002001: Weld SE container 99aa398e-5705-43df-bd74-714fae6dc440 shut down
BUILD SUCCESSFUL in 13s
4 actionable tasks: 1 executed, 3 up-to-date
That’s it! If there is a new bird you only have to create a new BirdImpl and annotate it with our BirType qualifier. No changes have to be made on the factory class. This is very useful, when you want to implement a module- or plugin-concept. Put new implementations in an independent jar on the classpath and the factory will find them automatically :)
The complete gradle project is here on github.com/elmolm