What Makes Jersey Interesting: Injection Providers
Ok, let’s get back to the geeky stuff.
Another interesting thing about Jersey (in addition to parameter classes) is the way it uses dependency injection.
Jersey doesn’t have an abstract base class for resources, like Rails’
ActionController::Base
or Restlet’s Resource
. This removes the obvious way
of retrieving information about the incoming request: from the base class.
Take this Rails controller action, for example:
def show
render(:text => "You asked for #{request.url}")
end
request
is a method defined on ActionController::Base
which returns an
object encapsulating the information about the current HTTP request.
But how would you test this action in isolation? Hopefully the base class provides an easy way of passing in a mock request object, or else you’re stuck modifying instance variables or partially mocking the class you’re trying to test.
So what’s a better way of doing that?
Jersey avoids this situation by injecting the required information into the resource class:
public String show(@Context UriInfo uriInfo) {
return "You asked for " + uriInfo.getAbsolutePath();
}
Jersey detects the @Context
annotation and automatically injects a UriInfo
with the current request’s data into the method. You can do this for
HttpHeaders
, SecurityContext
, and a few other Jersey classes.
When it comes time to test this class, it’s a simple matter of making a mock
UriInfo
and passing it in:
@Test
public void itReturnsTheRequestURI() throws Exception {
final MyResource resource = new MyResource();
final UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getAbsolutePath()).thenReturn("/wooooo");
assertThat(resource.show(uriInfo), is("You asked for /wooooo"));
}
Because the resource class doesn’t go out and get the UriInfo
itself, it’s
much easier to test.
What takes this feature from Neat to Indispensable is the fact that Jersey opens this infrastructure to you: it’s easy to write your own injection providers to peel arbitrary bits of an HTTP request off and inject them into your resources.
1… 2… 3… Example time!
As an easy example, let’s take the request’s locale, as determined by its
Accept-Language
header. Out of the box, Jersey gives us access to this via
HttpHeaders#getAcceptableLanguages()
, which returns a list of Locale
instances in order of preference.
But it’s a tedious thing to have our resource class have an HttpHeaders
instance injected and then get the first item from the
getAcceptableLanguages()
results:
public String uppercase(@Context HttpHeaders headers) {
return "this is lowercase".toUppercase(headers.getAcceptableLanguages().get(0));
}
In order to test that, we’ll have to come up with an HttpHeaders
mock and stub
its getAcceptableLanguages()
to return a list of locales. Bleagh.
Update: If you’re wondering why you’d need a Locale
to convert a string to
uppercase, check out the Turkish language. The uppercase version of i
(U+0069
) is İ (U+0130
), and the lowercase version of I (U+0049
) is ı
(U+0131
). It matters.
Now make it uglier
In keeping with my “so what if it looks good on a slide” motif here, I’m also going to throw in error handling: what if the user doesn’t specify a locale?
public String uppercase(@Context HttpHeaders headers) {
final List<Locale> locales = headers.getAcceptableLanguages();
final Locale selectedLocale;
if (locales.isEmpty()) {
selectedLocale = Locale.US;
} else {
selectedLocale = locales.get(0);
}
return "this is lowercase".toUppercase(selectedLocale);
}
That’s goddamn horrible–we’ll have to test that logic all over the place,
lest we end up throwing an IndexOutOfBoundsException
because someone’s HTTP
client has funny ideas about valid locales are.
It would be nice to have the locale-selecting code in its own class, and to do
that in the same way that our HttpHeaders
instance was injected.
I assume you have some kind of plan
In order to do that we’ll need to write two things:
- A class implementing
Injectable<Locale>
, which will be responsible for extracting aLocale
from an HTTP request context. - A class implementing
InjectableProvider<Locale>
, which will be responsible for injecting instances of #1.
Jersey has a specific class to handle the first responsibility:
AbstractHttpContextInjectable
which is used to inject information from the
HTTP context into resource classes. The second responsibility is simple enough
to not require a template base class.
Ok, let’s do it
Luckily for us, we can kill two birds with one stone and implement both complementary responsibilities in a single class:
@Provider
public class LocaleProvider
extends AbstractHttpContextInjectable<Locale>
implements InjectableProvider<Context, Type> {
@Override
public Injectable<E> getInjectable(ComponentContext ic, Context a, Type c) {
if (c.equals(Locale.class)) {
return this;
}
return null;
}
@Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
@Override
public Locale getValue(HttpContext c) {
final Locales locales = c.getRequest().getAcceptableLanguages();
if (locales.isEmpty()) {
return Locale.US;
}
return locales.get(0);
}
}
This is kind of a complicated class. Let’s cover a few things.
- The
@Provider
annotation marks it so that Jersey will add it as an injection provider. - The
getInjectable
method checks to see thatc
isLocale
–that is, if Jersey is asking this provider if it can inject aLocale
. If so, it returns itself to do the injection. Otherwise, it returnsnull
to indicate there’s nothing it can inject. - The
getScope
method indicates that the returned injectable is only meaningful on a per-request basis. - The
getValue
method is where the real logic happens. Once theLocaleProvider
instance has been returned fromgetInjectable
, Jersey passes it anHttpContext
, which contains all the relevant information about the HTTP request. It returns the most-preferred locale, including error handling.
When we put LocaleProvider
in a package that Jersey’s configured to scan, we
can reduce our resource class logic to this:
public String uppercase(@Context Locale locale) {
return "this is lowercase".toUppercase(locale);
}
Then our test looks like this:
@Test
public void itReturnsAnUppercaseString() {
final MyResource resource = new MyResource();
assertThat(
resource.uppercase(Locale.CANADA),
is("THIS IS LOWERCASE")
);
}
But I don’t think we’re done yet.
How many times you wanna write this thing
Much like parameter classes, our code gets cleaner the more injection providers we write, so we need to extract out the guts into a base class:
public abstract class AbstractInjectableProvider<E>
extends AbstractHttpContextInjectable<E>
implements InjectableProvider<Context, Type> {
private final Type t;
public AbstractInjectableProvider(Type t) {
this.t = t;
}
@Override
public Injectable<E> getInjectable(ComponentContext ic, Context a, Type c) {
if (c.equals(t)) {
return getInjectable(ic, a);
}
return null;
}
public Injectable<E> getInjectable(ComponentContext ic, Context a) {
return this;
}
@Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
}
Now our provider is sleek and shiny:
@Provider
public class LocaleProvider extends AbstractInjectableProvider<Locale> {
public LocaleProvider() {
super(Locale.class);
}
@Override
public Locale getValue(HttpContext c) {
final Locales locales = c.getRequest().getAcceptableLanguages();
if (locales.isEmpty()) {
return Locale.US;
}
return locales.get(0);
}
}
Now both our resource class and our LocaleProvider
are composed and testable.
I love it when a plan comes together.
tl;dr
Jersey has an internal dependency injection system which allows you to write small, focused classes to extract aspects of an HTTP request–in our case, the request’s locale–and inject them into your resource classes as an object of an appropriate type. This makes for smaller, more composed, more testable resource classes, which in turn makes for an application which is easier to change.