Dynamic CDI simple factory pattern with AnnotationLiteral

Dynamic CDI simple factory pattern with AnnotationLiteral

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


Florian Schmidt

Senior Java EE Software Engineer, Swift Enthusiast, Techie, Managing Partner Sneed IT UG, Founder Smipty