(See
a more recent posting on this subject.)
I have a body of code that already benefits from Guice dependency injection, and I want to migrate it from a servlet-based architecture to Restlet without losing those benefits. I've had some success, and I figured I'd report on it.
I
don't mean that I wanted to use Guice to wire up an object graph of Restlets (Components, VirtualHosts, Applications, etc.). It's straightforward enough to use the builder-like API of Restlet, and such code is nicely confined in a few places where it doesn't bother the meat of the application, its resources.
What I
do want is to have those resources created by Guice, so that they can be constructed with dependencies injected into them. With the Finder approach, resource constructors get a Context, a Request, and a Response, but that's it. I suppose I could find a way to stash an Injector in one of these so that resources could look up the dependencies they need, but I wanted something more direct. I came up with the following scheme, which seems to be working pretty well:
I use a custom Finder that knows how to look up a Handler/Resource by class or Guice key -- instances of these custom Finders are provided by a FinderFactory:
public interface FinderFactory {
Finder finderFor(Key<? extends Handler> key);
Finder finderFor(Class<? extends Handler> cls);
}
The class RestletGuice has static methods named createInjector that parallel those of the Guice class. The difference is that it adds bindings for Context, Request, Response, and FinderFactory. In my Guiced version of the FirstStepsApplication from restlet.org, the following lines create an Injector with some bindings and then look up the FinderFactory that will produce Finders that will be able to make use of those bindings. (These bindings are not necessarily good practice; they just demonstrate the technique.)
Injector injector = RestletGuice.createInjector(new AbstractModule() {
public void configure() {
bind(Handler.class)
.annotatedWith(HelloWorld.class)
.to(HelloWorldResource.class);
bindConstant()
.annotatedWith(named(HelloWorldResource.HELLO_MSG))
.to("Hello, Restlet-Guice!");
}
});
FinderFactory factory = injector.getInstance(FinderFactory.class);
HelloWorldResource is slightly changed. It has a private final field, msg, that is used to generate the text representation. The msg field is initialized via an injected value.
@Inject public HelloWorldResource(@Named(HELLO_MSG) String msg,
Request request,
Response response,
Context context) {
super(context, request, response);
this.msg = msg;
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
}
static final String HELLO_MSG = "hello.message";
This all comes together in the Application class when attaching a Finder for the default routing.
Finder finder = factory.finderFor(Key.get(Handler.class, HelloWorld.class));
Router router = new Router(getContext());
router.attachDefault(finder);
return router;
I read this as "Route any request for this application to whatever resource is bound to @HelloWorld-annotated Handlers, injecting that resource's dependencies," which was exactly what I wanted.
For those who don't want to have to call a special RestletGuice method, there is a public class FinderFactoryModule extending AbstractModule that can be used with
Guice.createInjector(...)
to get the same effect.
FinderFactoryModule also implements FinderFactory, so you can construct an instance in your Restlet wiring code and use it right away. You can then use the FinderFactoryModule to create an injector explicitly.
As a special convenience, if you don't use a FinderFactoryModule to create an injector, one will be created implicitly the first time one of its Finders is used to find a target Handler/Resource. This encourages a style where each Application gets its own implicit Injector by creating a local FinderFactoryModule and using it to create Finders.
That's basically it. It works with plain Guice 1.0 and Restlet 1.1 (since it uses Handler as the base type for all Resources). I haven't had time to package it nicely, but you can follow the links below for the actual code.
Sources (links are out of date, use
this instead):
FinderFactory.java
FinderFactoryModule.java
RestletGuice.java
Example (links are out of date, use
this instead):
FirstStepsApplication.java
HelloWorld.java
HelloWorldResource.java
Main.java
Update (August 2008)
Chris Lee discovered that Context.getCurrent() doesn't work reliably as of 1.1m5; the workaround is to use Application.getCurrent().getContext(). A more comprehensive fix, that I hope to post soon, would involve letting subclasses of FinderFactoryModule override the methods that create the Providers for Request, Response, and Context.
Update (2009-Feb-3)
Leigh Klotz has a
workaround for using Guice Finders with WadlApplication. And I still haven't had time to package any of this more nicely. (
Feb 7: Jérôme Louvel took Leigh's suggestion and checked it in on the Restlet trunk.)