Tuesday, June 19, 2007

Drink More Guice...

Okay, okay...this post is, seriously, a long time coming. I have not posted anything for a while for a number of reasons. I could give the details, but there aren't enough of you out there who are going to care. So, I'll skip it and get into the goodies.


Reading this may actually kill two birds with a single stone for many of you. For the example project that I put together here, I made use of the Echo2 framework. There are a number of reasons that I chose to do this. First of all, I think that dependency injection (D.I.) can be particularly beneficial when working in a component model. I think the switch to a real component model in Java GUI development is long overdue, particularly in the world of web UI development. So, I am hoping that this example will convey the ease with which a simple UI can be created using a framework like Echo2 or GWT as well as provide a few simple, yet useful, ways to leverage D.I. when creating a user interface.


We have all seen dependency injection brought into the main stream by the Spring Framework. As many of its proponents will tell you, Spring is a very powerful and highly configurable tool, whose usefulness goes way beyond simple dependency injection. Many people see Guice as a competitor to Spring, and in some ways it is. However, Guice is not nearly as broad in functionality as Spring. Guice is aimed at being fast and simple for dependency injection only. One immediately evident difference between Spring and Guice is Guice having fully embraced annotations in its implementation of DI. While I must point out that while the JavaConfig project has brought annotation based configuration to Spring, the implementation is, arguably, more complex and results in a more intrusive presence in the most common circumstances where DI is being used. If you are familiar with Spring and JavaConfig, much of this will become evident as I continue.


Enough of this blathering, let's look at some code. I am going to start with what many tutorials will save to the end: the point of injection. Ideally, D.I. should be something that is as invisible as possible. Far too often you see people directly accessing a framework's DI context directly in several different places in their application code. This is a situation that I will try to avoid in this tutorial. To that end, I am going to try to find a place where injection can happen once and the effects will flow throughout the rest of the application. In an Echo2 application this choice is simple, every Echo2 application instance is feed off and attached to the user session via a WebContainerServlet implementation:


package com.amateuratbest.examples.guice;

import nextapp.echo2.app.ApplicationInstance;
import nextapp.echo2.webcontainer.WebContainerServlet;

import com.amateuratbest.examples.guice.layout.LayoutModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.servlet.ServletModule;

/**
* This is where all the Guice-iness goes on. We will look for our injector
* in the ServletContext, create it if necessary and then inject our newly
* constructed ApplicationInstance. All done!
*
* @author mkimberlin
*/
public class TestGuiceServlet extends WebContainerServlet {

private static final long serialVersionUID = 2715828643772899566L;

/* (non-Javadoc)
* @see nextapp.echo2.webcontainer.WebContainerServlet#newApplicationInstance()
*/
@Override
public ApplicationInstance newApplicationInstance() {
TestGuiceApplication newApp = new TestGuiceApplication();
// This uses the Injector class name as the attribute name...
Injector injector = (Injector) getServletContext().getAttribute(
Injector.class.getName());
if (injector == null) {
injector = Guice.createInjector(new Module[] {new LayoutModule(), new ServletModule() });
getServletContext().setAttribute(Injector.class.getName(), injector);
}
injector.injectMembers(newApp);
return newApp;
}
}

In order to avoid constructing the injector more than once, I am keeping it in the ServletContext. If one isn't in context when I need it, it can be instructed by specifying the configuration modules that contain the desired configuration. Yes, you read that right. The configuration is done in Java, not XML. For the purposes of this example, I have a set of two modules that will be used for injection. In this case, I have one application specific module (LayoutModule) and one Guice-provided module (ServletModule). If you are working with a web application, you will almost definitely want to include this second module, as it defines injection scopes for request and session as well as providing bindings for injection into the request, session or response. It should be noted that there is nothing preventing the use of different modules as dictated by your applications needs. Module selection could be done based on user preferences, branding, or any number of other concerns. The main thing here is that, if I do things right, we should never see an injector again.


Now that we've seen our "point of entry", as it were, let us grab a quick look at those configuration modules:


package com.amateuratbest.examples.guice.layout;

import nextapp.echo2.app.Column;
import nextapp.echo2.app.SplitPane;

import com.amateuratbest.examples.guice.annotations.NamedAnnotation;
import com.google.inject.AbstractModule;

/**
* This class contains the DI bindings for the main layout components.
*
* @author mkimberlin
*/
public class LayoutModule extends AbstractModule {
public static final String BANNER = "banner";
public static final String NAVIGATION = "navigation";

@Override
protected void configure() {
bind(Column.class).annotatedWith(new NamedAnnotation(BANNER))
.to(BannerColumn.class);
bind(SplitPane.class).annotatedWith(new NamedAnnotation(BANNER))
.to(BannerSplitPane.class);
bind(SplitPane.class).annotatedWith(new NamedAnnotation(NAVIGATION))
.to(NavigationSplitPane.class);


// Note that if you uncomment the following lines you will get a compile
// time error. Guice's use of generics means a far less error prone
// configuration process. If this were done in a config file, you would
// have to wait until run time to discover your mistake.
//
//bind(Column.class).annotatedWith(new NamedAnnotation(BANNER))
// .to(BannerSplitPane.class);
}
}

This module, lovingly dubbed "LayoutModule" extends the abstract class AbstractModule that is provided as a part of Guice. As you can see, you only need to override the configure() method and specify the bindings that you would like Guice to honor when doing injection based on this configuration. The first two calls to bind() produce bindings for the given classes for any case where the target is annotated with a named annotation constructed with the name "banner". Using named annotations like this is simply one way to accomplish multiple bindings for the same class. The third call to bind() produces a second binding for the SplitPane class. However, this binding only applies when the target was annotated with a the NamedAnnotation for "navigation".


The section of code that has been commented out can be uncommented to see how Guice's "config in code" approach results in a less error-prone configuration. Static typing results in a compile time error if you have configured a binding that doesn't make sense.


This next piece of code has a couple of methods that are requesting injection. What is worth attention in these is the fact that there was no binding necessary. If you look at the earlier configuration module, you will not find anything that creates a binding for these classes. Crazy Bob Lee and his ilk at Google appear to have put a good deal of effort in to minimizing the amount of configuration you have to do (of which this is but one example). In this particular case, I just need to make sure that any new instances get a rootWindow and layoutPane. Since Window has an available default constructors, Guice will just take care of things for us without having to be told. LayoutPane is a similar, but not identical circumstance, but I will investigate that in a moment.


package com.amateuratbest.examples.guice;

import nextapp.echo2.app.ApplicationInstance;
import nextapp.echo2.app.Window;
import nextapp.echo2.app.componentxml.ComponentXmlException;
import nextapp.echo2.app.componentxml.StyleSheetLoader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amateuratbest.examples.guice.layout.LayoutPane;
import com.google.inject.Inject;

/**
* Very simple implementation of the Echo2 ApplicationInstance. Normally, we
* would not want to have a check that is "injection aware", per se...but I did
* here for "instructional" purposes. Now I think I'll use some "more quotes",
* "just because" i think they are "groovy".
*
* @author mkimberlin
*/
public class TestGuiceApplication extends ApplicationInstance {
private static Log log = LogFactory.getLog(TestGuiceApplication.class);

private Window rootWindow;

private LayoutPane layoutPane;

/* (non-Javadoc)
* @see nextapp.echo2.app.ApplicationInstance#init()
*/
@Override
public Window init() {
try {
setStyleSheet(StyleSheetLoader.load("/style.xml", Thread.currentThread().getContextClassLoader()));
} catch (ComponentXmlException e) {
log.warn("Could not load default styles for routine builder.");
}

if(rootWindow == null) {
rootWindow = new Window();
log.debug("Injection Failed...");
} else {
log.debug("Injection Worked...");
}
rootWindow.setContent(layoutPane);
return rootWindow;
}

public Window getRootWindow() {
return rootWindow;
}

@Inject
public void setRootWindow(Window rootWindow) {
this.rootWindow = rootWindow;
}

public LayoutPane getLayoutPane() {
return layoutPane;
}

@Inject
public void setLayoutPane(LayoutPane layoutPane) {
this.layoutPane = layoutPane;
}
}

Unlike with Window, Guice is not using a default constructor for LayoutPane:


package com.amateuratbest.examples.guice.layout;

import nextapp.echo2.app.ContentPane;
import nextapp.echo2.app.SplitPane;

import com.amateuratbest.examples.guice.ComponentUtil;
import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
* Constructs and contains the main layout components of the application.
* You will notice the constructor parameters are named. In this example
* I'm using named mostly to avoid writing a bunch of custom annotations.
* This may or may not be the right approach for your project.
*
* @author mkimberlin
*/
public class LayoutPane extends ContentPane {
private SplitPane bannerSplitPane;
private SplitPane navigationSplitPane;

@Inject
public LayoutPane(
@Named(LayoutModule.BANNER) SplitPane bannerSplitPane,
@Named(LayoutModule.NAVIGATION) SplitPane navigationSplitPane) {
super();
this.bannerSplitPane = bannerSplitPane;
this.navigationSplitPane = navigationSplitPane;

add(bannerSplitPane);
bannerSplitPane.add(navigationSplitPane);
}

/**
* @return the navigationSplitPane
*/
public SplitPane getNavigationSplitPane() {
return navigationSplitPane;
}

/**
* @param navigationSplitPane the navigationSplitPane to set
*/
public void setNavigationSplitPane(SplitPane navigationSplitPane) {
ComponentUtil.replaceChild(bannerSplitPane, this.navigationSplitPane,
navigationSplitPane);
this.navigationSplitPane = navigationSplitPane;
}

/**
* @return the bannerSplitPane
*/
public SplitPane getBannerSplitPane() {
return bannerSplitPane;
}

/**
* @param bannerSplitPane the bannerSplitPane to set
*/
public void setBannerSplitPane(SplitPane bannerSplitPane) {
ComponentUtil.replaceChild(this, this.bannerSplitPane, bannerSplitPane);
this.bannerSplitPane = bannerSplitPane;
}
}

In this case I have made Guice aware of a constructor available for use during injection with the @Inject annotation. Furthermore, I have annotated the parameters of said constructor in such a way as to trigger the bindings that I presented in the LayoutModule.


Another instance of injection in this example application that deserves calling is in the NavigationSplitPane. Let's take a look at its constructor:


@Inject
public NavigationSplitPane(Column buttonColumn, ContentPane mainPane, ContentDAO contentDAO) {
...
}

Here again, there is a constructor being marked for injection, and again there are default constructors being used without configuration. The last parameter deserves a few words, and not only because it is highly irregular to construct a GUI component by handing it a DAO...but that's not the point here. ;)


As you might expect, ContentDAO is an interface. So, how did Guice know what to do for it?


package com.amateuratbest.examples.guice.dao;

import com.amateuratbest.examples.guice.dao.mock.ContentDAOImpl;
import com.google.inject.ImplementedBy;

/**
* Simple DAO interface with a default implementation annotated for Guice.
* Using the ImplementedBy annotation allows the developer to supply a default
* implementation in the event that there is no explicit binding for an
* interface. This is the case 90% of the time for most projects.
*
* @author mkimberlin
*/
@ImplementedBy(ContentDAOImpl.class)
public interface ContentDAO {
public String getContent();
}

Some people are going to balk at this right off of the bat. "WHY does my interface know about its implementation?!?" However, I think you have to see this as something that is available for your convenience (and boy is it convenient) and not a required part of working with Guice. If we developers are honest with ourselves, or simply inspect our code, it is clear that most interfaces we create use a single implementation 80+ percent of the time. This makes those 80+ percent easy. Obviously if this is an interface that you are going to package as a public API for some B2B system, you probably want to avoid this type of dependency. Most of the time that is not the case, however. Most of your interfaces only exist on the server and only have one implementation in the first place. So, it is up to you to decide when using this "default implementation" feature is appropriate


I have not presented all of the code contained in the example application in this tutorial. I would encourage you to download and deploy the application for yourself. Plop the war in tomcat and off you go...all of the dependencies are included in the project zip file. You will then be the proud papa (or mama) of a rich web application developed with only a few Java classes, and without touching any javascript directly, which manages to hydrate itself successfully with only one initial point of injection.

2 comments:

Formation Continue said...

Ok, I may be late on this :-)

Just a quick question: the way that you have set up the application (injecting panels and such), the entire application will get instantiated (all panels anyways). Isn't this sort of heavy on memory ?

Wouldn't it be better to have the injector in a "component factory" that panels would call to create child panels and such ?

A Man said...

Absolutely. I really did not intend this as a real world framework for building applications. More of a "see how nice these two can be together". I've been considering revisiting this topic with some more real world suggestions. Productive work tends to get in the way though. :)