Wanted: javax.interceptor extension for Guice
I'm feverishly preparing Guice for the 2.0 release later this summer, and tonight I scanned through our
issues list. There's a whole bunch of good features that I won't get to before our release. So I'm looking for Guice users to help out with development!
Introducing javax.interceptor
javax.interceptor is a fairly-simple method interception package for modern Enterprise Java stacks. These
examples show how it works. Create a class with an
@Interceptors
annotation:
@Interceptors(AuditInterceptor.class)
public class AccountBean implements Account {
private int balance = 0;
public void deposit(int amount) {
balance += amount;
}
public void withdraw(int amount) {
balance -= amount;
}
}
Then create the interceptor:
public class AuditInterceptor {
@AroundInvoke
public Object audit(InvocationContext invocationContext) throws Exception {
System.out.println("Invoking method: " + invocationContext.getMethod());
return invocationContext.proceed();
}
}
If everything works as intended, the interceptor's
audit()
method will intercept all calls to
deposit()
and
withdraw()
.
Guice's MethodInterceptor
Guice has another API for this,
MethodInterceptor. Guice uses
Matchers to support arbitrary selection of interceptable methods.
Wanted: an Extension for javax.interceptor
I believe this feature could be implemented as an extension to Guice. The extension would certainly require some
clever tricks, but it should not require changes to the Guice internals. Here's what I guess the implementation could look like:
public class JavaxInterceptorModule extends AbstractModule {
public void configure() {
JavaxInterceptor interceptor = new JavaxInterceptor();
injectMembers(interceptor);
bindInterceptor(Matchers,any(), new InterceptMethodsMatcher(), interceptor);
}
static class JavaxInterceptor {
/**
* Loop over all the injector's bindings, looking for @Interceptor.
* Then verify that the interceptor classes are either injectable (ie.
* they have bindings), or they have a no-arg constructor. This
* isn't strictly necessary, but it allows us to fail faster, which is
* always a Guicy thing to do.
*/
@Inject void initialize(Injector injector) { ... }
/**
* Validate that the target method is intercepted. We need to
* consider just-in-time bindings that might not have been
* checked during initialize(). We also need to check for the
* ExcludeClassInterceptors etc. annotations.
*
* <p>Instantiate all of the injectors for this method, then
* run 'em. We'll need our own implementation of InvocationContext
* to pass to the interceptors.
*/
Object invoke(MethodInvocation invocation) { ... }
}
}
Of course it's not this simple. The implementation needs be tested to be consistent with the Java EE implementations. It needs to have reasonable error handing. And there's nuances related to inheritance etc. It also needs thorough unit tests.
Recruiting Contributors
Does writing this code sound fun to you? If it does, we'd love your help. Post your interest on
the bug! You'll need to
checkout the Guice code, write the code and tests, and upload a patch. I'll code review the patch (a fairly involved process) and we'll iterate until it's perfect.
In return, you'll get to see your
@author
line in the Guice source code. You'll probably learn a lot about AOP, Guice and reflection. It's resume padding. And of course, coding is its own reward.
# posted by Jesse Wilson
on Friday, May 30, 2008
3 comments
post a comment
Bug pattern: multiple ways to represent the same data
There's a class of bugs that come up when one logical datatypes has representations in multiple classes. The best example of this is
1
vs.
1L
. Both ones represent the same data. But
new Integer(1)
is not equal to
new Long(1)
according to the corresponding equals() methods.
Calling
contains(1)
on a
List<Long>
compiles just fine, it just won't ever return true. Similarly for
Map.get()
and
Set.contains()
. Anything that depends on
equals()
is broken if you mix different types to express 'one'.
The problem is that each defines an equals method that is local to its class. This is a fair design - but as a consequence these types should not be mixed.
A short catalog of tricky types
...that can cause you pain if you mix them. These types can all represent the same logical value. But if you mix them, you will certainly get burned:
- "0" : Byte, Short, Integer, Long
- "0.0" : Float, Double
- "Jan 1, 1970 12:00.00am UTC" : Date, long, Calendar
- "http://publicobject.com" : URI, URL
- "integer type" : int.class, Integer.class
- "ABC" : StringBuffer, StringBuilder, CharSequence, String
- "natural order" : Comparators.naturalOrder(), null
- "String[].class" : GenericArrayType, Class (both of which implement Type)
A shorter catalog of good types
Fortunately, in a few places the JDK has interfaces that dictate how
equals
and
hashCode
must be implemented. As a consequence, you can freely intermix these types without consequence:
- Sets: HashSet, LinkedHashSet, TreeSet
- Maps: ConcurrentHashMap, HashMap, Collections.emptyMap()
- Lists: ArrayList, LinkedList, Vector, Arrays.asList
Defining this behaviour for interfaces is somewhat difficult - use these classes as a guide. All implementations must implement the spec exactly or behaviour will be unreliable.
Recommendations
Avoid creating classes that allow one logical datatype to be represented by different classes. If you must, consider writing an interface to specify equals and hashCode at that level.
Choose a preferred, canonical form for your data. For example, if you consider 'null' and 'empty string' to be equal, choose one form and stick to it. Throw IllegalArgumentExceptions to callers that use the wrong one. If you're using collections, always use the canonical type for inserts and lookups.
Use an smart IDE like
IntelliJ IDEA. It'll warn you when you mix types.
An Aside...
It turns out that Guice 1.0 suffered an ugly bug because of this problem. You can represent arrays in two different ways using Java 5's Types API. Either as an instance of
Class
or as an instance of
GenericArrayType
. The two are equivalent but not equals(). As a consequence, some injections would incorrectly fail with 'missing bindings' exceptions.
# posted by Jesse Wilson
on Wednesday, May 28, 2008
0 comments
post a comment
Guice talk, tomorrow morning at Google I/O
Bob and I are giving a talk at Google's developer conference tomorrow. From the
session summary:
Wed 11:15am - 12:15pm
Bob Lee, Jesse Wilson
Guice (pronounced 'juice') is a Jolt award-winnning, lightweight dependency injection framework for Java 5 and above. Put simply, Guice alleviates the need for factories and the use of new in your Java code. Think of Guice's @Inject as the new new. You will still need to write factories in some cases, but your code will not depend directly on them. Your code will be easier to change, unit test and reuse in other contexts.
# posted by Jesse Wilson
on Wednesday, May 28, 2008
0 comments
post a comment
Calling a method that always throws
Kasper
complains that it's hard to call methods that always throw. In particular, code like this won't compile because
findGetter
is missing a return statement:
public Method findGetter() throws CsvException {
try {
return type.getMethod("get"
+ fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1));
} catch(SecurityException e) {
noSuchGetter(e);
} catch (NoSuchMethodException e) {
noSuchGetter(e);
}
}
public void noSuchGetter(Exception cause) throws CsvException {
String message = String.format("Unable to lookup getter"
+ " for field %s on %s", fieldName, type.getName());
throw new CsvException(message, cause);
}
The problem is that
noSuchGetter
always throws, but javac doesn't consider that when building this code. Making that method
private
or
final
doesn't help -- javac does not look at the body of a method when deciding whether the callers are valid.
Adding
return null;
to the end of this method appeases the compiler, but it sure isn't pretty:
public Method findGetter() throws CsvException {
try {
return type.getMethod("get"
+ fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1));
} catch(SecurityException e) {
noSuchGetter(e);
} catch (NoSuchMethodException e) {
noSuchGetter(e);
}
return null; /* never executes! (this method never returns null.) */
}
As Kasper explains, this workaround sucks! In particular, it suggests to the code reader that he should check for null when calling this method. We need to add a big ugly comment to flag what's going on. Another workaround is to throw
AssertionError
s on unreachable lines. This is an improvement but we still have dead code in our program. This line won't ever execute, adding noise to
code coverage reports.
Fortunately, there's a clever fix. Declare that
noSuchGetter
returns a
RuntimeException
and prefix call to it with
throw
:
public Method findGetter() throws CsvException {
try {
return type.getMethod("get"
+ fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1));
} catch(SecurityException e) {
throw noSuchGetter(e);
} catch (NoSuchMethodException e) {
throw noSuchGetter(e);
}
}
public CsvException noSuchGetter(Exception cause)
throws CsvException {
String message = String.format("Unable to lookup getter"
+ " for field %s on %s", fieldName, type.getName());
throw new CsvException(message, cause);
}
Now the code reader can see that the
noSuchGetter
calls always throw without reading that method's body. And the compiler will complain if you add any code after that call. We can optionally tweak this so
noSuchGetter
returns
CsvException
rather than throwing it.
Methods that always throw should return what they throw. If the method always throws a
RuntimeException
, we'd return that type.
# posted by Jesse Wilson
on Thursday, May 22, 2008
11 comments
post a comment
Overriding Bindings in Guice
In our functional tests, we prefer to launch an application that closely resembles the production one. This means using the production modules in a proper staging environment. Even with automating scripts, setting up the environment is time consuming and tedious - it requires database install and configuration, permissions, SSL certificates, and remote services to be running and available:
public class ProductionModule extends AbstractModule {
protected void configure() {
bind(LoginService.class).to(RemoteLoginService.class);
bind(DataStore.class).to(MySqlDatabase.class);
bind(BillerConnection.class).to(SecureBillerConnection.class);
bind(LoggingEngine.class).to(DistributedLoggingEngine.class);
}
}
Since proper functional tests are so labour intensive, we take shortcuts! We can make a decent approximation of production by replacing some production services with lightweight fakes. For example, all of our functional tests require a login server to authenticate users. But for most tests login isn't interesting and this is just a wasted effort. We'd rather just fake login for unrelated functional tests.
Modular Modules
The best solution to this problem is to split the login stuff into its own module:
public class ProductionLoginModule extends AbstractModule {
protected void configure() {
bind(LoginService.class).to(RemoteLoginService.class);
}
}
In the functional test server we can use a
FakeLoginModule
rather than the
ProductionLoginModule
. It provides the same bindings, but its entirely in-memory and configuration free. This approach is simple, manageable and maintainable. It's the best way to mix production and test code.
Overrides via Inheritance
There's also a quick-and-dirty approach to creating test versions of servers. Extract each offending binding to its own method, and override that method in a subclass:
public class ProductionModule extends AbstractModule {
protected void configure() {
bindLoginService();
bind(DataStore.class).to(MySqlDatabase.class);
bind(BillerConnection.class).to(SecureBillerConnection.class);
bind(LoggingEngine.class).to(DistributedLoggingEngine.class);
}
protected void bindLoginService() {
bind(LoginService.class).to(RemoteLoginService.class);
}
}
public class TestModule extends ProductionModule {
@Override protected void bindLoginService() {
bind(LoginService.class).to(FakeLoginService.class);
}
}
This does this trick, but it's not elegant. We end up with different subclasses for each combination of fake bindings. It's fragile and makes for more work to divide the module later.
New: overrideModule
Sam Berlin has contributed a feature to Guice that provides a simple mechanism to replace bindings. To use it, we create a module with binding overrides:
public class OverridesModule extends AbstractModule {
@Override protected void configure() {
bind(LoginService.class).to(FakeLoginService.class);
}
}
We can combine our original, unmodified
ProductionModule
with the overrides to create a third module:
Module functionalTestModule
= Guice.overrideModule(new ProductionModule(), new OverridesModule());
In this example, we get the
DataStore
,
BillerConnection
and
LoggingEngine
bindings from
ProductionModule
, but the
FakeLoginService
from
OverridesModule
. The bindings in the overriding module replace bindings of the same key from the production module.
It's not as clean as modular modules, but it is more convenient. In particular, this approach doesn't require changes to the production module - great if that's provided by another team or a third party. It's also handy for testing the module itself!
This code is in SVN now if you're interested. It uses the new
commands API to get a lot done in a
small amount of code.
Fair Warning: I've been blogging about a lot of new Guice APIs lately.
These APIs aren't final. If you depend on unreleased APIs from Guice svn, be aware that those APIs will probably change for 2.0.
# posted by Jesse Wilson
on Saturday, May 17, 2008
0 comments
post a comment
MapBinder checked in!
Building out the
Multibindings extension, David P. Baker has written a new class that simplifies Map binding.
MapBinder
uses an API that's consistent with
Multibinder
:
public class SnacksModule extends AbstractModule {
protected void configure() {
MapBinder<String, Snack> mapBinder
= MapBinder.newMapBinder(binder(), String.class, Snack.class);
mapBinder.addBinding("twix").toInstance(new Twix());
mapBinder.addBinding("snickers").toProvider(SnickersProvider.class);
mapBinder.addBinding("skittles").to(Skittles.class);
}
}
Like Multibinder...
Keys must be non-null and unique. Values must be non-null. Values get resolved at injection-time, so they can be scoped. Bindings from different modules will be aggregated, which makes it usable for an application registry.
Unlike Multibinder...
For the example above, an extra binding is made for
Map<String, Provider<Snack>>
. This makes it so that if values are expensive to provide, you can get just the ones you need.
This API is available in Guice svn right now.
The API is subject to change before version 2. I suspect we'll probably rename
Multibinder
to either
SetBinder
or
MultiBinder
. Any preferences?
# posted by Jesse Wilson
on Friday, May 16, 2008
3 comments
post a comment
Elite Guice 3: Private Bindings
In the
first and
second parts of this series, I discussed hooks for extending Guice. In this post I introduce private bindings.
Don't emulate Guice
The key to a good extension is tight integration with Guice. In particular, the extension shouldn't copy functionality from Guice; doing so can lead to subtle differences between the extension and Guice itself. The assisted inject extension reimplements constructor injection and that causes problems:
- Standard Guice can inject private constructors, but I forgot to do configure this in assistedinject.
- AOP doesn't work for assistedinject-created objects
These differences aren't dealbreakers, but they do make the extension harder to use, and they violate the principal of least surprise. A better assistedinject would have used Guice's constructor injection.
The Injector is just a bunch of bindings
In the
Multibindings extension, we secretly create a binding for each element in the set. These bindings are implementation details of the extension, but they need to be bound in the shared injector.
We have to somehow create the bindings without a chance of conflicting with user-supplied bindings. If
Multibinder
and the user both bound the same key, injector creation would fail.
Private Annotations to the rescue
The fix is to create a binding annotation type with a narrow scope, such as package scope. Other code won't have access to this annotation, and therefore we can be confident that only our extension can create bindings with this annotation.
In the Multibindings extension, there's a custom annotation called
@Element
that's secretly applied to all elements. Each annotation instance is given a unique ID to ensure that the hosting keys are distinct. Unless they're using a tool that interrogates the Injector, the users of
Multibinder
never see these bindings. They're created internally and used internally. But they allow all of Guice's features to work naturally on the bound elements.
Implementing the annotation type is code-intensive, but there's
thorough instructions that describe how equals, hashCode and even toString() should be implemented.
This technique is also used in
InterceptingInjectorBuilder, in order to let
InjectionController work its magic.
# posted by Jesse Wilson
on Sunday, May 11, 2008
0 comments
post a comment
Why no Multibindings for Lists or Arrays?
The Multibindings code I
checked in last week has been well received. There's even a
screencast introducing the new code.
But there are missing features. We're still figuring out exactly what's the right balance of functionality and simplicity. One particular omission is the lack of support for Lists and arrays.
This is intentional. The deal breaker with Lists and arrays is that they imply an explicit order of their elements. But Multibinder has no idea how to order the elements. It could try to keep elements of the same module in the same order they were bound, but all bets are off when the bindings come from multiple modules. And it would be
very annoying if reordering your modules changed the behaviour of your program.
But if you really want a List...
Its not to hard to make a List<String> binding using the Set<String> binding that
Multibinder
gives you.
bind(new TypeLiteral<List<String>>() {})
.to(new Provider<List<String>>() {
@Inject Provider<Set<String>> setProvider;
public List<String> get() {
/* if we wanted to, we could sort the list somehow */
return Collections.unmodifiableList(
new ArrayList<String>(setProvider.get());
}
});
Using this technique, you could also bind
Map<K, V>
if you've got a binding to
Set<Map.Entry<K, V>>
. We're thinking about adding built-in support for Maps, but this will work until we do.
# posted by Jesse Wilson
on Wednesday, May 07, 2008
1 comments
post a comment
Elite Guice 2: Binding de-duplication
In
part one, I showed how to initialize a Guice extension using the
@Inject
tag on a method. In this post I'm going to demonstrate a Guice for deduplicating bindings.
What is binding duplication?
One of the features of the
Multibindings Extension is that there's no central coordination. Each module contributes its own bindings, and they all get amalgamated into a single collection. Suppose you have these modules:
public class CandybarModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
}
}
public class ChipsModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().to(Pringles.class);
multibinder.addBinding().toInstance(new Doritos());
}
}
Out of this API, our
Multibinder
implementation needs to bind
Set<Snack>
exactly once. This is tricky! For example, suppose our
newSetBinder
method was implemented like this:
public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
return new Multibinder(type);
}
This won't work. We call
newSetProvider
from both the
CandybarModule
and the
ChipsModule
, and Guice complains because we're binding
Set<Snack>
twice. This is binding duplication.
If Guice had an duplicate-binding API
Guice doesn't have this API, but what we kind of find ourselves wanting is a way to query the binder for its bindings thus-far:
public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
if (!binder.getBindings().containsKey(getSetKey(type))) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
}
return new Multibinder(type);
}
This would work, but in general it's a pretty terrible idea. It makes it easy to write fragile modules - modules that depend on the order they are installed in. And it also changes the module from being static configuration (good!) to a dynamic-runtime configuration (bad!). So we're glad this API doesn't exist.
But in this one case, we really wish we could prevent our binding from being performed twice.
Modules as value objects
The solution to this problem is a bit surprising, but I think it's quite elegant. Guice conveniently allows the same
module to be installed twice. This is necessary so that both your
PizzaServletsModule
and your
PizzaDatabaseModule
can install your
PizzaDomainModule
.
Rather than binding the
Set<Snack>
directly on the binder, we create special
Module
class whose only job is to create that binding. And then we give that module a proper
equals()
and
hashCode()
methods, so that any two instances that bind the same type are equal.
Our final code looks like this:
public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.install(new MultibindingModule(getSetType(), getProviderType()));
return new Multibinder(type);
}
The
Module
trick allows us to decentralize the binding. And that means less configuration code, and that means less code. Hooray for Guice.
# posted by Jesse Wilson
on Sunday, May 04, 2008
0 comments
post a comment
Elite Guice 1: Initialize your extension
I recently wrote some fairly, um,
extreme code for the Guice
Mutibindings extension. That code makes use of several tricks that you might find useful in your own Guice extensions. In this series I'm going to show-and-tell the various clever code tricks from
Multibinder.java.
Initialize an extension
Guice doesn't have a plug-in API and it probably never will. But if it did, it would probably provide a mechanism for you to initialize your plugin:
public interface GuicePlugin {
void initialize(Injector injector) throws ConfigurationException;
}
This interface doesn't exist. But that's fine, because Guice does something better - it injects members into every bound instance and provider instance at injector-creation time. And where there is injection, there is initialization. There's two steps to getting Guice to call your initialize method:
- Bind an instance, or a provider instance.
Usually your extension is going to bind something anyway. Multibinder binds its Set
, AssistedInject binds its Factory
, ThrowingProviders binds its ThrowingProvider
. If you have absolutely nothing to bind, create a class and bind an instance of that.You must bind either an instance, a provider instance, or an eager singleton. These are the only things that get injected at injector-creation time.
- Create a
@Inject
initialize() method on the bound instance.
This method gets called at injector-creation time. This is that important stage after all modules have been executed but before the Injector has been returned to its creator. Your initialize method can take any injectable objects as parameters - usually the Injector is a useful choice, since it allows your extension to review its complete set of bindings.The method can also throw exceptions to indicate that it's improperly configured. Guice 1.0 has a bug where sometimes these exceptions don't halt injection. But recent snapshots have fixed this problem and you can now reliably use exceptions to fail if your extension is misconfigured. AssistedInject uses this feature to fail if your assisted constructor cannot be injected.
Example
In
Multibinder.java
, we bind a provider instance for the set:
public void configure(Binder binder) {
...
binder.bind(setKey).toProvider(setProviderInstance);
}
That provider has our initialize() method. In this case, the initialize method loops through all of the injector's bindings, collecting providers for the elements of the collection:
/**
* Invoked by Guice at Injector-creation time to prepare providers for each
* element in this set. At this time the set's size is known, but its
* contents are only evaluated when get() is invoked.
*/
@Inject void initialize(Injector injector) {
providers = new ArrayList<Provider<T>>();
for (Map.Entry<Key, Binding> entry : injector.getBindings().entrySet()) {
if (keyMatches(entry.getKey())) {
Binding<T> binding = (Binding<T>) entry.getValue();
providers.add(binding.getProvider());
}
}
}
Hooray, our extension is initialized. In future posts I intend to discuss duplicate binding, private bindings, and perhaps annotation nesting.
# posted by Jesse Wilson
on Thursday, May 01, 2008
0 comments
post a comment
Guice Multibindings extension checked in!
As previously alluded to, I've started work on the
Multibindings API. I've checked in the initial implementation, and I'm pretty excited about it. You can grab it from
svn.
This feature is ideal for lightweight plugin-type architectures, where you've got multiple modules each contributing Servlets, Actions, Filters, Components or even just names.
The Multibinder Javadoc:
An API to bind multiple values separately, only to later inject them as a complete collection. Multibinder is intended for use in your application's module:
public class SnacksModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder
= Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
multibinder.addBinding().to(Skittles.class);
}
}
With this binding, a
Set<Snack>
can now be injected:
class SnackMachine {
@Inject
public SnackMachine(Set<Snack> snacks) { ... }
}
Create multibindings from different modules is supported. For example, it is okay to have both
CandyModule
and
ChipsModule
to both create their own
Multibinder<Snack>
, and to each contribute bindings to the set of snacks. When that set is injected, it will contain elements from both modules.
Elements are resolved at set injection time. If an element is bound to a provider, that provider's get method will be called each time the set is injected (unless the binding is also scoped).
Annotations are be used to create different sets of the same element type. Each distinct annotation gets its own independent collection of elements.
Elements must be distinct. If multiple bound elements have the same value, set injection will fail.
Elements must be non-null. If any set element is null, set injection will fail.
# posted by Jesse Wilson
on Thursday, May 01, 2008
4 comments
post a comment