Sunday 8 May 2016

In My Toolbox: CDI


One thing that Java developers may not complain about their ecosystem is choice. There is so much choice in Java, at all levels, that it may seem sometimes daunting. What to choose when getting the job done?

Take Dependency Injection*), for example. Besides well established names like J2EE servers or Spring, we have lightweight, standalone solutions such as Google Guice or PicoContainer.

The disadvantages of Spring, Guice or PicoContainer are … well … their lack standardization. If you choose to work with any of them then you’re bound to their semantics (although Spring starts to move towards supporting the standard semantics of dependency injection from Java Enterprise) which is the open-source variant of vendor lock-in.

So, for standard-compliant dependency injection we’re pretty much stuck with an application server … or not? Actually not, because – thanks to the wonderful standardisation process of Java – we do have server-independent dependency injection. Java CDI is the name and it was formalised over various JSRs (I know of 299, 330, 346 and 365).

To sweeten the deal, there is a good-quality implementation: JBoss Weld. Weld can be (and it is) used in application servers, web servers or servlet containers or (and this is what I am talking about) it can be fired up straight away from a plain, simple, Java executable.

How It Works

I’ll show portions of the code of a small web server application which I wrote today. It embeds Jetty for web infrastructure, it makes use of Apache Wicket as a web framework and it encapsulates command line parsing (i.e. Apache CLI) in a class aptly named as CmdLine:
@Singleton public class App {
   
    @Inject private SimpleLog           log;   
    @Inject private CmdLine             cmdLine;
    @Inject private Instance<WebServer> webServer;
   
    public static void main(String[] args) {
        CmdLine.setArgs(args);

        final Weld weld = new Weld().packages(App.class);
        
try(WeldContainer cnt = weld.initialize()) {
            final App app = cnt.select(App.class).get();
            if (app.cmdLine.hasHelp()) {
                app.cmdLine.printHelp();
            } else {
                final WebServer srv = app.webServer.get();
                boolean         started = false;
                Exception       err = null;
                try {
                    srv.start();
                    started = true;
                } catch(Exception ex) {
                    err = ex;
                } finally {
                    if (started) {
                        try {
                            srv.stop();
                        } catch(Exception ex) {
                            err = ex;
                        }
                    }
                }
                if (err != null) {
                    app.log.err(err);
                }
            }
        }
    }
}

The WebServer class encapsulates a org.eclipse.jetty.server.Server instance:


@Singleton public class WebServer {
   @Inject private   SimpleLog   log;
    @Inject private   Server      server;
    @Inject @HttpPort private int port;

   public int getPort() { return port; }
   private boolean   started;
    public void start() {
        log.info("Starting web server at port " + getPort() + " ...");
        try {
            server.start();
            server.join();
            started = true;
            log.info("Web server started.");
        } catch(Exception ex) {
            log.err(ex);
        }
    }
    public void stop() {
        try {
            if (started) {
                log.info("Stopping web server ...");
                server.stop();
                log.info("Web server stopped ...");
            }
        } catch(Exception ex) {
            log.err(ex);
        } finally {
            started = false;
        }
    }
}

Where is the Server instance coming from (along with its HTTP port)? From a WebServerFactory instance (the nice part is that we don’t have to instantiate the factory, the container does it for us):

@Singleton public class WebServerFactory {
   @Inject private CmdLine cmdLine;
   @Produces @Singleton
    public ServletHolder getServletHolder(WicketServlet servlet) {
        final ServletHolder result = new ServletHolder();
        result.setServlet(servlet);
        return result;
    }   
    @Produces @Singleton
    public ServletHandler getServletHandler(ServletHolder holder) {
        final ServletHandler result = new ServletHandler();
        final ServletHolder[] holders = { holder };
        result.setServlets(holders);
        return result;
    }
    @Produces @Singleton
    private ServletContextHandler getServletContextHandler(
        ServletHandler handler) {
        final ServletContextHandler result = new ServletContextHandler();
        result.setContextPath("/wicket");
        result.setHandler(handler);
        return result;
    }
    @Produces @Singleton
    private SessionHandler getSessionHandler(ServletContextHandler handler) {
        final SessionHandler result = new SessionHandler();
        result.setHandler(handler);
        return result;
    }
    @Produces @Singleton
    private Server getServer(SessionHandler handler,
                             @HttpPort int port)
        throws IOException {
        final Server result = new Server(port);
        result.setHandler(handler);
        result.setStopAtShutdown(true);
        return result;
    }
    @Produces @Singleton @HttpPort
    public int getPort() throws IOException {
        return cmdLine.getFreePort(23);
    }
}

Conclusion

Besides the abundance of choices that the Java developer is being pampered with at all levels, nearly all variants have a standard specification, a reference implementation and (usually) many additional, standard-compliant implementations.

Dependency Injection is no exception: standard CDI has a lightweight, easy to use and standard-compliant reference implementation (JBoss Weld) that the Java developer can use straight away, without the need of a fully-blown J2EE-compliant application server.


*) Dependency injection is not the same as Inversion of Control. We can have one without the other although they best work together.

No comments:

Post a Comment