Extending Guice

Posted on by

Guice is a framework that I had been looking forward to trying out for a while, but until recently I never had the opportunity.  Previously I had mostly used Spring (with a dash of PicoContainer), so when I got the opportunity to start using Guice, I naturally had a number of my favourite Spring features in mind as I started using it.  Very quickly I found myself wanting an equivalent of Springs DisposableBean.  Guice is focussed on doing one thing and doing it well, and that thing is dependency injection.  Lifecycle management doesn’t really come into that, so I am not surprised that Guice doesn’t offer native support for disposing of beans.  There is one Guice extension out there, Guiceyfruit, that does offer reasonably complete per scope lifecycle support, however Guiceyfruit requires using a fork of Guice, which didn’t particularly appeal to me.  Besides, Guice is very simple, so I imagined that providing my own simple extensions to it would also be simple.  I was right.

Though, to be honest, while the extensions themselves are simple, it wasn’t that simple to work out how to write them.  On my first attempt, I gave up after Googling and trying things out myself for an hour.  On my second attempt, I almost gave up with this tweet.  But, I stuck with it, and eventually made my breakthrough. The answer was in InjectionListener. This listener is called on every component that Guice manages, including both components that Guice instantiates itself, and components that are provided as instances to Guice.

Supporting Disposables

So, I had my disposable interface:

public interface Disposable {
  void dispose();
}

and I wanted any component that implemented this interface to have their dispose() method called when my application shut down.  Naturally I had to maintain a list of components to dispose of:

final List<Disposable> disposables = Collections.synchronizedList(new ArrayList());

Thread safety must be taken into consideration, but since I only expected this list to be accessed when my application was starting up and shutting down, a simple synchronized list was suffcient, no need to worry about performant concurrent access.

My InjectionListener is very simple, it just adds disposables to this list after they’ve been injected:

final InjectionListener injectionListener = new InjectionListener<Disposable>() {
  public void afterInjection(Disposable injectee) {
    disposables.add(injectee);
  }
};

InjectionListener‘s are registered by registering a TypeListener that listens for events on types that Guice encounters.  My type listener checks if the type is Disposable (this actually isn’t necessary because we will register it using a matcher that matches only Disposable types, but it is defensive to do the check), and if so registers the InjectionListener:

TypeListener disposableListener = new TypeListener {
  public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
    if (Disposable.class.isAssignableFrom(type.getRawType())) {
      TypeEncounter<Disposable> disposableEncounter = (TypeEncounter<Disposable>) encounter;
      disposableEncounter.register(injectionListener);
    }
  }
}

Now I can register my TypeListener.  This is done from a module:

bindListener(new AbstractMatcher<TypeLiteral<?>>() {
      public boolean matches(TypeLiteral<?> typeLiteral) {
        return Disposable.class.isAssignableFrom(typeLiteral.getRawType());
      }
    }, disposableListener);

The last thing I need to do is bind my collection of disposables, so that when my app shuts down, I can dispose of them:

bind((TypeLiteral) TypeLiteral.get(Types.listOf(Disposable.class)))
                .toInstance(disposables);

So now when my app shuts down, I can look up the list of disposables and dispose of them:

for (Disposable disposable : ((List<Disposable>) injector.getInstance(
    Key.get(Types.listOf(Disposable.class)))) {
  disposable.dispose();
}

If you decide to use this code in your own app, please be very wary of a potential memory leak. Any beans that are not singleton scoped will be added to the disposable list each time they are requested (per scope).  For my purposes, all my beans that required being disposed of were singleton scoped, so I didn’t have to worry about this.

Supporting annotation based method invocation scheduling

Happy that I now had a very simple extension with very little code for supporting automatic disposing of beans, I decided to try something a little more complex… scheduling. My app contains a number of simple scheduled tasks, and the amount of boilerplate for scheduling each of these was too much for my liking. My aim was to able to do something like this:

@Schedule(delay = 5L, timeUnit = TimeUnit.MINUTES, initialDelay = 1L)
def cleanUpExpiredData() {
  ...
}

(Yep, this app has a mixture of Scala and Java.) So, I started with my annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule {
    long delay();
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    long initialDelay() default 0;
}

The main difference this time is that I’m not listening for events on a particular type, but rather I want to check all types to see if they have a @Schedule annotated method. This is a little more involved, so I’m going to have a scheduler service that does this checking and the scheduling. Additionally it will make use of the disposable support that I just implemented:

public class SchedulerService implements Disposable {
  private final ScheduledExcecutorService executor = Executors.newSingleThreadScheduledExecutor();

  public boolean hasScheduledMethod(Class clazz) {
    for (Method method : clazz.getMethods()) {
      Schedule schedule = method.getAnnotation(Schedule.class);
      if (schedule != null) {
        return true;
      }
    }
    return false;
  }

  public void schedule(Object target) {
    for (final Method method : target.getClass().getMethods()) {
      Schedule schedule = method.getAnnotation(Schedule.class);
      if (schedule != null) {
        schedule(target, method, schedule);
      }
    }
  }

  private void schedule(final Object target, final Method method, Schedule schedule) {
    executor.scheduleWithFixedDelay(new Runnable() {
      public void run() {
        method.invoke(target);
      }, schedule.initialDelay(), schedule.delay(), schedule.timeUnit());
  }

  public void dispose() {
    executor.shutdown();
  }
}

Now in my module I instantiate one of these services:

final SchedulerService schedulerService = new SchedulerService();

I then implement my InjectionListener:

final InjectionListener injectionListener = new InjectionListener() {
  public void afterInjection(Object injectee) {
    schedulerService.schedule(injectee);
  }
}

and my TypeListener:

TypeListener typeListener = new TypeListener() {
  public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
    if (schedulerService.hasScheduledMethod(type.getRawType()))  {
      encounter.register(injectionListener);
    }
  }
}

And then all I have to do is register my type listener, and also my scheduler service (so that it gets disposed properly):

  bindListener(Matchers.any(), typeListener);
  bind(SchedulerService.class).toInstance(schedulerService);

Conclusion

Although Guice doesn’t come with as much as Spring does out of the box, it is very simple to extend to meet your own requirements. If I were needing many more container like features, then maybe Spring would be a better tool for the job, but when I’m just after a dependency injection framework with a little sugar on top, Guice is a very nice and much lighter weight solution.

5 thoughts on “Extending Guice

  1. hallo vielleicht kannst du mir mal helfen ich spiele nano farm und seid 2 tagen geht der beim laden nur bis 63% UND DANN bleibt der stehen habe alles neu gemacht und es geht nicht und bei kniffel party ist das gleiche problem
    was kann ich noch machen das ich die spiele wieder spielen kann

  2. Thanks a million :)
    After Googling zillion of dirth I was lucky to read your post.

  3. Thanks! But what if a Disposable happens to come out of a Provider? Your InjectionListener will not catch it then, because the Disposable is not injected, but the provider is.
    Maybe what this use case would need is rather the ProvisionListener of Guice 4.0? I’m not familiar with it, but I have a similar use case and immediately ran into the Provider problem.

  4. For that rationale, many people are choosing a elimination product rather, that
    has the additional benefit of making it possible for them the chance to remove their pores and skin tags
    in personal, in your own home. It is an autoimmune disorder in which the pigment-producing cells
    are damaged. Damage caused by a skin burn depends on the depth, location as well
    as the amount of surface area that was involved.

  5. Babes of mothers who smoked during pregnancy
    havee a higher risk of dying at birth or shortly after birth and SIDS (sudden infant death
    syndrome) riskk is higher as well. All these things
    will encourage you to kuck the habit and also keep away from elesctronic cigarettes; http://www.youtube.com,.
    Postmenopausal women smokers have lower bone
    density.

Leave a Reply

Your email address will not be published. Required fields are marked *


*