Every now and then I find myself scratching through some of my old code to find that example “where I did that factory like thing”.

When this happened again last week I decided to just find all examples and create an example project and blog post about it.

So in this post I:

  • start with a plain “vanilla” Java SE factory example
  • then one using Java SE SPI
  • CDI on Java SE
  • CDI on Java EE
  • EJB on Java EE
  • Dynamic SPI on Java SE
  • and lastly SPI on Java EE

The example.

This example app is a very simple “Hello World” that you can pass in a name and there are multiple ways to say hello.

example_app

The Greeting Service gets an instance of the Greeting Factory. It can then ask the factory for a Greeting (interface) by name, and the factory will return the correct implementation.

There are 3 concrete implementations:

All the source code for this blog is available in Github:

    git clone https://github.com/phillip-kruger/factories-example

The Greeting interface:

    public interface Greeting {
        public String getName();
        public String sayHello(String to);
    }

Vanilla

This basic Java SE app has a main method that allows you to pass in your name and the way(s) you want to be greeted.

The factory is a basic if-statement to get the correct implementation:

    public Greeting getGreeting(String name){
        if(name.equalsIgnoreCase("BugsBunny")){
            return new BugsBunny();
        }else if(name.equalsIgnoreCase("Afrikaans")){
            return new Afrikaans();
        }else {
            return new English();
        }
    }

An example of a concrete implementation, English:

    public class English implements Greeting {

        @Override
        public String sayHello(String to) {
            return "Good day " + to + ".";
        }

        @Override
        public String getName() {
            return "English";
        }
    }

Run the example:

In the vanilla folder:

    mvn clean install

This will build the project and also run the app. The log will output:

    SEVERE: 
    Good day Phillip.
    Goeie dag Phillip.
    Eeee, what's up Phillip ?

You can also run this outside of maven:

    java -jar target/vanilla-1.0.0-SNAPSHOT.jar World BugsBunny
    SEVERE: 
    Eeee, what's up World ?

Also see


Service provider interface (SPI)

The above example means I can very easily add another implementation, and update the if statement to return that implementation when asked.

However, it’s that if-statement that we want to improve. We want to get to a point where I can add new implementations without having to modify existing code. All I want to do is add the new implementation.

SPI is part of Java SE and is an API that allows you to build plug-able extentions.

Breaking the application up into modules.

The first thing we are going to do is to break the application up into modules:

  • API - This will contain the Greeting interface (our contract)
  • Engine - This will contain the Service and Factory (and the default English implementation)
  • Other implementations - All other implementations become their own module (So one for Afrikaans and one for Bugs Bunny etc.)

This already means I can add a new implementation by just creating a new module, not having to touch the code, just update the dependencies:

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>spi-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>spi-impl-afrikaans</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>spi-impl-bugsbunny</artifactId>
            <version>${project.version}</version>
        </dependency>
        
    </dependencies>

The mapping file

The concrete implementations need to register their Greeting class as a implementation by adding a file in /src/main/resources/META-INF/services/ called com.github.phillipkruger.factory.api.Greeting (The fully qualified name of the interface)

And the contents of the file is the name of the implementation, example Bugs Bunny:

    com.github.phillipkruger.factory.impl.BugsBunny

The factory

The factory now needs to get all instances of Greetings:

    ServiceLoader<Greeting> loader = ServiceLoader.load(Greeting.class);
    Iterator<Greeting> greetingIterator = loader.iterator();
    while (greetingIterator.hasNext()) {
        Greeting greeting = greetingIterator.next();
        loadedGreetings.put(greeting.getName(), greeting);
    }

Now we got rid of the if-statement in the factory.

Run the example:

In the spi folder:

    mvn clean install

This will build the project and also run the app. The log will output:

    SEVERE: 
    Good day Phillip.
    Goeie dag Phillip.
    Eeee, what's up Phillip ?

You can also run this outside of maven:

    java -jar spi-engine/target/spi-engine-1.0.0-SNAPSHOT-jar-with-dependencies.jar Johny Afrikaans
    SEVERE: 
    Goeie dag Johny.

Also see


Contexts and Dependency Injection (CDI)

The latest version of CDI allows you to use CDI in Java SE. To create a factory, we are going to create our own annotation as part of the API called GreetingProvider:

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface GreetingProvider {
        String value();
    }

Where the value is the name of the implementation.

And a literal implementation for the above:

    @AllArgsConstructor
    public class GreetingProviderLiteral extends AnnotationLiteral<GreetingProvider> implements GreetingProvider {

        private final String name;

        @Override
        public String value() {
            return this.name;
        }
    }

This will allow us to annotate any concrete implementation (that is now a RequestScoped CDI Bean) with @GreetingProvider, example English:

    @GreetingProvider("English")
    @RequestScoped
    public class English implements Greeting {

        @Override
        public String sayHello(String to) {
            return "Good day " + to + ".";
        }

        @Override
        public String getName() {
            return "English";
        }
    }

The factory change to find all @GreetingProvider classes:

    public class GreetingFactory {

        @Inject @Any
        private Instance<Greeting> greetings;

        public Greeting getGreeting(String name) {
            Instance<Greeting> instance = greetings.select(new GreetingProviderLiteral(name));
            if(!instance.isUnsatisfied()){
                Greeting provider = instance.get();
                return provider;
            }else{
                return new English();
            }
        }

    }

So now we do not need the SPI mapping file anymore, there is no if-statement in the factory, but we still have to update the dependencies to include all implementations we want.

Run the example:

In the cdi folder:

    mvn clean install

This will build the project and also run the app. The log will output:

    SEVERE: 
    Good day Phillip.
    Goeie dag Phillip.
    Eeee, what's up Phillip ?

You can also run this outside of maven:

    java -jar cdi-engine/target/cdi-engine-1.0.0-SNAPSHOT-jar-with-dependencies.jar Charmaine BugsBunny
    SEVERE: 
    Eeee, what's up Charmaine ?

Also see


CDI on Java EE

We can also use CDI to create the solution on an Application Server. We will now make the entry point a REST service (rather than a main method) so we need to create and add an ApplicationConfig to enable JAX-RS:

    @ApplicationPath("/api")
    public class ApplicationConfig extends Application {
    }

The GreetingService now becomes a REST Resource that allows you to do a GET passing the name as a PathParam and optional ways to greet as QueryParam:

    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public class GreetingService {

        @Inject
        private GreetingFactory factory;

        @GET
        @Path("{to}")
        public String sayHello(@PathParam("to") String to, @QueryParam("way") List<String> way){
            //....
        }
    }

The factory and annotated RequestScoped CDI Bean implementations stay exactly the same as the CDI on Java SE example.

Run the example:

This example can run on 3 different application servers (just to keep ourselves honest)

(You do not have to download, install or configure anything, the maven build will do that)

In the javaee-cdi folder:

    mvn clean install -P wildfly

or

    mvn clean install -P liberty

or

    mvn clean install -P payara

In all 3 cases maven will:

So in the log you will see (something like):

===============================================
["BugsBunny","Afrikaans","English"]
===============================================

===============================================
["Eeee, what's up Phillip ?","Goeie dag Phillip.","Good day Phillip."]
===============================================

If you run Payara, the server does not shutdown, so you can also manually test the factory:

    wget -qO- http://localhost:8080/javaee-cdi-engine/api/Donald?way=BugsBunny
    ["Eeee, what's up Donald ?"]

EJB on Java EE

Just to complete the examples, here is how you can do this with EJB’s on Java EE (so no CDI - and thus also no custom annotation)

We just use JNDI to lookup a named EJB.

The GreetingService stays the same as the Java EE CDI example, so we still have a REST entry point. The concrete implementations now change to become EJBs, example English:

    @Stateless
    @EJB(beanInterface = Greeting.class, beanName = "English", name = "English")
    public class English implements Greeting {

        @Override
        public String sayHello(String to) {
            return "Good day " + to + ".";
        }

        @Override
        public String getName() {
            return "English";
        }
    }

The factory now does a JNDI lookup based on the bean name:

    @Log
    @Stateless
    public class GreetingFactory {

        @EJB(lookup = "java:module/English")
        private Greeting english; // default

        public Greeting getGreeting(String name) {
            Greeting g = lookup("java:module/" + name);
            if(g==null)return english;
            return g;
        }

        public List<Greeting> getAll(){

            List<Greeting> greetings = new ArrayList<>();

            try {

                InitialContext context = new InitialContext();

                NamingEnumeration<Binding> list = (NamingEnumeration<Binding>)context.listBindings("java:global/javaee-ejb-engine");    

                while (list.hasMore()) {
                    Binding next = list.next();
                    if(next.getName().endsWith(Greeting.class.getName())){
                        Greeting g = lookup("java:global/javaee-ejb-engine/" + next.getName());
                        if(g!=null && !greetings.contains(g))greetings.add(g);
                    }
                }


            } catch (NamingException e) {
                throw new RuntimeException(e);
            } 
            return greetings;
        }

        private Greeting lookup(String jndi){
            try {
                InitialContext context = new InitialContext();
                Object o = context.lookup(jndi);
                return (Greeting)o;
            } catch (NamingException e) {
                log.log(Level.SEVERE, "Could not lookup [{0}]", jndi);
                return null;
            }   
        }
    }

Run the example:

Similar to the Java EE CDI example, this runs on Wildfly Swarm, Open Liberty and Payara Micro

In the javaee-ejb folder:

    mvn clean install -P wildfly

(or -P liberty or -P payara)

In all 3 cases maven will:

So in the log you will see (something like):

===============================================
["BugsBunny","Afrikaans","English"]
===============================================

===============================================
["Eeee, what's up Phillip ?","Goeie dag Phillip.","Good day Phillip."]
===============================================

If you run Payara, the server does not shutdown, so you can also manually test the factory:

    wget -qO- http://localhost:8080/javaee-ejb-engine/api/Barney?way=Afrikaans
    ["Goeie dag Barney."]

Dynamic SPI

So up until now, the only thing we have to do when adding a new implementation is to create the module that contains the Greeting implementation and update the pom.xml to include the new dependency.

Next let’s see how to load the new implementation dynamically (so no need to update the dependency).

The implementations are exactly like the Java SE SPI example, including the mapping file, but now we can remove the modules as dependencies in the pom.xml:

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>dynamic-spi-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <!-- This will be loaded dynamically
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>dynamic-spi-impl-afrikaans</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>dynamic-spi-impl-bugsbunny</artifactId>
            <version>${project.version}</version>
        </dependency>
        -->
    </dependencies>

And the factory looks like this:

    public class GreetingFactory {

        private final Map<String,Greeting> loadedGreetings = new HashMap<>();

        public GreetingFactory(){

            URLClassLoader classloader = getURLClassLoader();

            ServiceLoader<Greeting> loader = ServiceLoader.load(Greeting.class, classloader);
            Iterator<Greeting> greetingIterator = loader.iterator();
            while (greetingIterator.hasNext()) {
                Greeting greeting = greetingIterator.next();
                loadedGreetings.put(greeting.getName(), greeting);
            }
        }

        public Greeting getGreeting(String name){
            if(loadedGreetings.containsKey(name)){
                return loadedGreetings.get(name);
            }else {
                return new English();
            }
        }

        private URLClassLoader getURLClassLoader(){

            File[] pluginFiles = getPluginFiles();

            ArrayList<URL> urls = new ArrayList<>();
            for(File plugin:pluginFiles){
                try{
                    URL pluginURL = plugin.toURI().toURL();
                    urls.add(pluginURL);
                }catch(MalformedURLException m){
                    log.log(Level.SEVERE, "Could not load [{0}], ignoring", plugin.getName());
                }

            }

            return new URLClassLoader(urls.toArray(new URL[]{}),GreetingFactory.class.getClassLoader());
        }

        private File[] getPluginFiles(){
            File loc = new File("plugins");
            File[] pluginFiles = loc.listFiles((File file) -> file.getPath().toLowerCase().endsWith(".jar"));
            return pluginFiles;
        }
    }

Basically, the factory will still use SPI’s ServiceLoader to load the available greetings, but we pass a custom ClassLoader in. We look for any jar file in the plugins folder and load those with URLClassloader.

This means I can now just create a new implementation module and drop the file in the plugins folder. Nice and plug-able.

Run the example:

In the dynamic-spi folder:

    mvn clean install

This will build the project and also run the app. The log will output:

    SEVERE: 
    Good day Phillip.
    Goeie dag Phillip.
    Eeee, what's up Phillip ?

You can also run this outside of maven:

    java -jar dynamic-spi-engine/target/dynamic-spi-engine-1.0.0-SNAPSHOT-jar-with-dependencies.jar Madonna BugsBunny
    SEVERE: 
    Eeee, what's up Madonna ?

Dynamic SPI on Java EE

Now whether this is a good idea is a different discussion, but just to show that it’s possible, we will now use dynamic SPI to load the implementations on an Application server. This means I can add a new implementation to a running server. So not only can I add a new implementation without touching the code or the dependencies, but I can also enable this new implementation without having to restart the application.

The implementations look exactly like the Java SE SPI example, the pom.xml does not contain any implementation modules, and we now have a new class that load the modules in the plugins folder:

It’s an ApplicationScoped CDI Bean that loads modules on startup. The modules can also be reloaded with REST:

    @Path("/pluginloader")
    @ApplicationScoped
    @Log
    public class PluginLoader {

        @Produces @Named("Greetings")
        private final Map<String,Greeting> loadedGreetings = new HashMap<>();

        public void init(@Observes @Initialized(ApplicationScoped.class) ServletContext context) {
            loadPlugins();
        }


        @GET
        @Path("/reload")
        public Response loadPlugins(){
            ClassLoader classloader = getClassLoader();

            ServiceLoader<Greeting> loader = ServiceLoader.load(Greeting.class, classloader);
            Iterator<Greeting> greetingIterator = loader.iterator();
            while (greetingIterator.hasNext()) {
                Greeting greeting = greetingIterator.next();
                log.log(Level.SEVERE, "Adding provider [{0}]", greeting.getName());
                if(!loadedGreetings.containsKey(greeting.getName())){
                    loadedGreetings.put(greeting.getName(), greeting);
                }
            }

            return Response.ok("ok").build();
        }

        private ClassLoader getClassLoader(){

            File[] pluginFiles = getPluginFiles();
            if(pluginFiles!=null){        
                ArrayList<URL> urls = new ArrayList<>();
                for(File plugin:pluginFiles){
                    try{
                        URL pluginURL = plugin.toURI().toURL();
                        urls.add(pluginURL);
                    }catch(MalformedURLException m){
                        log.log(Level.SEVERE, "Could not load [{0}], ignoring", plugin.getName());
                    }
                }
                return new URLClassLoader(urls.toArray(new URL[]{}),this.getClass().getClassLoader());
            }
            return this.getClass().getClassLoader();
        }

        private File[] getPluginFiles(){

            File loc = getPluginDirectory();
            if(loc==null)return null;
            File[] pluginFiles = loc.listFiles((File file) -> file.getPath().toLowerCase().endsWith(".jar"));
            return pluginFiles;

        }

        private File getPluginDirectory(){
            File plugins = new File("plugins");
            if(plugins.exists())return plugins;  

            return null;
        }
    }

All the loaded Greetings are available in a Map<String,Greeting> that can be injected into the factory:

    @RequestScoped
    @Log
    public class GreetingFactory {

        @Inject @Named("Greetings")
        private Map<String,Greeting> greetings;

        // ...
    }

Run the example:

Similar to the Java EE CDI and EJB example, this runs on Wildfly Swarm, Open Liberty and Payara Micro

In the javaee-spi folder:

    mvn clean install -P wildfly

(or -P liberty or -P payara)

In all 3 cases maven will:

So in the log you will see (something like):

===============================================
["BugsBunny","Afrikaans","English"]
===============================================

===============================================
["Eeee, what's up Phillip ?","Goeie dag Phillip.","Good day Phillip."]
===============================================

If you run Payara, the server does not shutdown, so you can also manually test the factory:

    wget -qO- http://localhost:8080/javaee-spi-engine/api/Frans?way=Afrikaans
    ["Goeie dag Frans."]

Currently in the plugins folder you will see the 2 known implementations (Afrikaans and Bugs Bunny):

    ls javaee-spi-engine/plugins/
    javaee-spi-impl-afrikaans-1.0.0-SNAPSHOT.jar  javaee-spi-impl-bugsbunny-1.0.0-SNAPSHOT.jar

That was copied there when we built those implementations.

Now, let’s leave the server running, and add a new way to greet you called Ali G. (see https://www.youtube.com/watch?v=b00lc92lExw)

    cd javaee-spi-impl-alig
    mvn clean install -P plugin

This will copy the Ali G implementation to the plugins folder.

Now let’s greet Frans again:

    wget -qO- http://localhost:8080/javaee-spi-engine/api/Frans?way=AliG
    ["Booyakasha Frans !"]

So we can add a new concrete implementation to a running server.

The End

That is it (for now). Any comments and your own examples welcome !