Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Saturday, July 4, 2015

Handling Classes Missing No-Arg Constructors in Google Guice

I've used Google Guice mostly to statically wire up related classes in my applications.  However, with the assistedinject extension, Google Guice can create any object on-the-fly including those without default constructors.  This is especially important these days because of

  1. Immutable classes requiring a full complement of fields at construction time, and
  2. Language support for Optionals.


In the past, an app might have been encouraged to use the JavaBeans spec in building domain objects: a no-arg constructor plus getters and setters.  In Google Guice, this is easily accommodated with an @Inject for an object or for a Provider.   With immutability and java.util.Optionals being promoted, you may find yourself without a no-arg constructor.  Google Guice handles this case with the AssistedInject and Assisted annotations.

To use AssistedInject, you'll need to add the guice-assistedinject library to your project.  If you're using Maven, add this to your POM.
  
<dependency>
  <groupId>com.google.inject.extensions</groupId>
  <artifactId>guice-assistedinject</artifactId>
  <version>4.0</version>
<dependency/>
  
Google Guice itself is still required.

Example

This example presents a notional logging subsystem implemented with Google Guice.  The following shows a main() using the logging subsystem.

public class AssistedInjectTestMain {

    public static void main(String[] args) {

        Injector injector = Guice.createInjector(new AssistedInjectTestModule());

        LogSubsystem log = injector.getInstance(LogSubsystem.class);

        log.debug("some debugging messages");

        log.info("an info statement");
    }
}

Running the main, gives the following results.

Results of Running Example Program
The main() gets a hold of an LogSubsystem object and calls a pair of methods: debug() and info().  The debug() call prints a "[DEBUG]", wraps the message in single quotes, and uppercases the whole String.  The info() call prints an "[INFO]", prepends the message with MSG=, and uppercases the whole String.

Internal Structure

This UML shows LogSubsystem implemented by LogSubsystemImpl.  LogSubsystemImpl uses a formatter class "LogFormatter" to apply the formatting to the LogSubsystem call's argument.  LogFormattter in turn uses a LogConfig object to communicate configuration information, the decision on whether to uppercase or lowercase the input.

Internal Structure of AssistedInject Example
LogSubsystem builds a LogFormatter on-the-fly using the LogFormatterFactory.  LogFormatter gets an instance to LogConfig using a simple un-assisted @Inject.

LogSubsystem

This is the code to the LogSubsystem interface.


public interface LogSubsystem {

    void debug(String message);

    void info(String message);

}

And this is the code to its implementation.  Notice that LogFormatterFactory is injected, not a LogFormatter or a Provider<LogFormatter>.  While I could certainly pass in everything to the underlying format() call at once, I'm opting to construct a specific formatter object with a level and optional formatter string.  I then call format() on the LogFormatter and the built-to-order object serves up the correctly-formatteed message

public class LogSubsystemImpl implements LogSubsystem {

    @Inject
    LogFormatterFactory logFormatterFactory;

    @Override
    public void debug(String message) {
        LogFormatter fmt = logFormatterFactory.create("DEBUG");
        String formattedMessage = fmt.format(message);
        System.out.println(formattedMessage);
    }

    @Override
    public void info(String message) {
        LogFormatter fmt2 = logFormatterFactory.create("INFO",  "msg=%s");
        String formattedMessage2 = fmt2.format(message);
        System.out.println(formattedMessage2);
    }

}

LogFormatter

This is the code to the LogFormatter interface.

public interface LogFormatter {

    String format(String message);
}

This is the code to the LogFormatter Implementation.

public class LogFormatterImpl implements LogFormatter {

    private String format = "'%s'";
    private String level;

    @Inject
    LogConfig logConfig;

    @AssistedInject
    public LogFormatterImpl(@Assisted("level") String level) {
        this.level = level;
    }

    @AssistedInject
    public LogFormatterImpl(@Assisted("level") String level, @Assisted("format") String format) {
        this(level);
        this.format = format;
    }

    @Override
    public String format(String message) {
        String formattedMessage = String.format( format, message);
        String retval = "[" + level.toUpperCase() + "] - " + formattedMessage;
        if( logConfig.useUpperCaseForLevel() ) {
            return retval.toUpperCase();
        } else {
            return retval.toLowerCase();
        }
    }
}

There are a pair of constructors tagged with @AssistedInject.  If I had only one constructor, then I could use @Inject (either Guice's or Javax's). I also have to mark the parameters with @Assisted this is to help Guice sort through the constructors.  If I had only a single constructor with an argument of a distinct type (one String versus two Strings), I could leave off the value ("level", "format") in the annotations.

This is the code from the factory.  Notice that interface uses the annotation values as well.

public interface LogFormatterFactory {

    LogFormatter create(@Assisted("level") String message);

    LogFormatter create(@Assisted("level") String message, @Assisted("format") String format);

}

An implementation of the factory does NOT need to be written.

LogConfig

I added a separate LogConfig class to demonstrate that the LogFormatter object returned to LogSubsystem is in fact a Guice object.  LogConfig -- not set explicitly by my program -- was successfully injected.  This is important because without AssistedInject, you would have to support some type of post-construction setter mechanism or expand the constructor, avoiding Google Guice.  LogFormatterFactory is not a way of creating an ordinary object.  Objects created with AssistedInject can have a full complement of the app's dependencies in them.


public interface LogConfig {

    boolean useUpperCaseForLevel();
}

And the implementation draws of off a constant set in the AbstractModule (presented later).
public class LogConfigImpl implements LogConfig {

    @Inject @Named("UseUpperCase")
    Boolean useUpperCase;

    @Override
    public boolean useUpperCaseForLevel() {
        return useUpperCase;
    }
}

Guice Module

Finally, the Guice AbstractModule subclass binds all of the implementations.  

LogSubsystem LogConfig, and the UseUpperCase Boolean are all bound in standard fashion.  LogFormatter is bound to LogFormatterImpl and tied to LogFormatterFactory.  The install() / FactoryModuleBuilder line is responsible for providing the implementation of the LogFormatterFactory used by the LogSubsystem.

public class AssistedInjectTestModule extends AbstractModule {

    public void configure() {

        bind(LogSubsystem.class).to(LogSubsystemImpl.class);
        bind(LogConfig.class).to(LogConfigImpl.class);

        bind(Boolean.class).annotatedWith(Names.named("UseUpperCase")).toInstance(Boolean.TRUE);

        install(new FactoryModuleBuilder()
                .implement(LogFormatter.class, LogFormatterImpl.class)
                .build(LogFormatterFactory.class));

    }
}

You can find all of the code in this blog post on GitHub.

Dependency injection was mainlined for JavaEE development back in 2007 with version 5.0.  Before that, the Spring framework provided a compelling and lightweight alternative and dependency injection played a key part.  Google Guice is even lighter and I think it's very suitable to JavaFX apps, where the lack of am application framework is a real drawback.  Moreover, recent coding trends in immutability and Optionals mean that no-arg classes are no longer a given.

The AssistedInject extension helps Guice bridge these trends and continue on with a solid, small footprint wiring strategy for software apps.

No comments:

Post a Comment