Inside Guice 2: exercising elements
Part 3 in a Series.Background: modelling configuration
One of the most distinct features of Guice is our fluent embedded domain specific language (DSL). By chaining method calls, Guice supports a wide combination of binding sources, targets and scopes:
bind(PaymentService.class)
.annotatedWith(Names.named("creditCard"))
.to(VisaPaymentService.class)
.in(RequestScoped.class);
bind(new TypeLiteral<Connector<Processor>>() {})
.annotatedWith(Online.class);
.toProvider(new CheckoutProcessorConnectorProvider());
bind(TransactionLog.class)
.toInstance(new InMemoryTransactionLog());
In our first implementation of the DSL, calls to bind() etc. configured the injector directly. This works well, but it's not very flexible. As long as creating injectors is
all that we do with modules, it's sufficient.
The Elements SPI
In Guice 2, the DSL is used to build an intermediate configuration model. Calls to bind() etc. create
Element
instances. For example, the three statements above will create a
LinkedKeyBinding
, a
ProviderInstanceBinding
and an
InstanceBinding
. The API to get the configuration model from a module is called
Elements.getElements().
By converting the method calls into value objects, we get an opportunity to inspect and transform them. For example, the following prints all of the keys bound in a module:
public static Set<Key<?>> getBoundKeys(Module module) {
Set<Key<?>> result = newHashSet();
for (Element element : Elements.getElements(module)) {
if (element instanceof Binding) {
result.add(((Binding) element).getKey());
}
}
return result;
}
Internally, we use this API to simplify injector creation. Now we can process elements in order we want, even when that's different from the order that the user has specified. We prefer to handle scope registrations before bindings, and bindings before injection requests.
These elements are useful in other APIs. For module overrides, we take elements from two modules and compute a union. We can also use elements to unit test modules without having their dependencies on hand. They'll also come in handy for development tools.
By converting method calls into value objects, we can do powerful new things.
# posted by Jesse Wilson
on Thursday, May 21, 2009
0 comments
post a comment
Inside Guice 2: injecting null
Part 2 in a Series.Background: marking nulls
Null is bad. Most of the time, use of
null
indicates clumsy modeling. Instead of null collections, use empty ones. Instead of a null service, use a no-op implementation. The JDK gets this wrong for comparators by using
null
as a stand-in for
natural order; the result is special cases instead of polymorphism.
To help you to detect null problems sooner, Guice
refuses to inject null by default. When asked to do so, it will fail with a
ProvisionException
instead. This increases your confidence in your app by eliminating an entire category of runtime problems.
But sometimes — and only sometimes — null is necessary. Value objects don't lend themselves to the null object pattern. And in some cases you can be sure that a null reference won't be used. To make intentional null use explicit, you can use one of several
@Nullable
annotations, including those from
FindBugs,
IntelliJ or old snapshots of
Google Collections. These will soon be standardized by another
@Nullable
in
JSR 305.
Injecting null
Guice tolerates
null
to be injected wherever a parameter is annotated
@Nullable
:
public Person(String firstName, String lastName, @Nullable Phone phone) {
this.firstName = checkNotNull(firstName, "firstName");
this.lastName = checkNotNull(lastName, "lastName");
this.phone = phone;
}
But which
@Nullable
?
Rather than arbitrarily choosing an
@Nullable
to support (and alienating everyone else), Guice permits any
Nullable
annotation to be used. And I do mean
any: it scans the injected parameter or field's annotations, looking at their names. If any has the simple name
Nullable
, it is honoured. This means that even your company's internal
com.company.util.Nullable
will work.
More generally
This trick works particularly nicely for annotations, since they tag objects without adding behaviour. It also avoids an aggravating problem, where code that
looks right doesn't
work right. Consider:
import com.google.inject.Inject;
import javax.annotation.Named;
public class RealPaymentService implements PaymentService {
@Inject
public RealPaymentService(@Named("creditcard") Processor processor) {
...
}
}
This code appears legitimate, and it might even execute without error. But the wrong
@Named
annotation is applied! The unreleased
javax.annotation.Named
is incompatible with Guice and has no effect. Since it
looks the same as
com.google.inject.name.Named
, there's potential for error.
When applying annotations, be careful about overlapping names. When processing them, be mindful of mistakes! What's the annotation-equivalent of a precondition? If a user applies an annotation incorrectly, is it detected?
Part 3.
# posted by Jesse Wilson
on Tuesday, May 19, 2009
4 comments
post a comment
Inside Guice 2: binder sources
To celebrate the
release of Guice 2.0, I'd like to showcase my favourite parts of the new code. In this
N-part series, I'll go deep into the implementation details to explain the clever and interesting code within the project. I'll focus on general techniques that you can use in your own applications.
Background: Getting sources
When you invoke methods on a
Binder
(or invoke them indirectly, via
AbstractModule
), Guice captures the caller's source. This is used to create javac-like error messages in the event of a configuration problem. For example, the following binding is illegal because
Executor
is an interface without an implementation:
class BadModule extends AbstractModule {
protected void configure() {
bind(Executor.class);
}
}
When this faulty module is used to create an injector, Guice reports the problem with the source line where it occurred:
Exception in thread "main" com.google.inject.CreationException: Guice creation errors:
1) No implementation for java.util.concurrent.Executor was bound.
at com.publicobject.blog.Scratch$1.configure(Scratch.java:18)
1 error
To obtain the offending source line, Guice grabs a stacktrace using
new Throwable().getStackTrace()
and scans through its frames to find the logical source. It has to ignore some classes:
AbstractModule.java
,
RecordingBinder.java
, etc.
The stacktrace grabbing trick is
extremely cool, but it's not sufficient. Guice can't always know the full set of classes that it should skip. Extensions like
Multibinder create bindings on their user's behalf and need to omit additional classes from the trace (
RealMultibinder.java
,
Multibinder.java
). And
Provider Methods specify their source as a method name instead of relying on a stack trace.
We need an API to specify which classes to skip in stacktraces, and another API to specify a source object directly.
Binder Sources
The difficulty in finding a nice API for configuring sources is that it's naturally temporal. We want to specify a source, use it for a binding or two, and then reset it back:
public void configure(Binder binder) {
Method method = ...
Object previous = binder.getSource();
binder.setSource(method);
try {
binder.bind(method.getGenericReturnType())
.toProvider(new ProviderMethod(method));
} finally {
binder.setSource(previous);
}
}
Yuck.
Taking inspiration from
List.subList(), our approach is to instead return a
new binder that works with the provided source:
public void configure(Binder binder) {
Method method = ...
Binder sourcedBinder = binder.withSource(method);
sourcedBinder.bind(method.getGenericReturnType())
.toProvider(new ProviderMethod(method));
}
Every binding, injection or configuration applied to
sourcedBinder
will use the user-specified source. But the passed-in binder is untouched. With some inlining and the binder's existing embedded DSL, the whole thing can be a single statement:
public void configure(Binder binder) {
Method method = ...
binder.withSource(method)
.bind(method.getGenericReturnType())
.toProvider(new ProviderMethod(method));
}
This new API is cute and fits nicely with the existing code.
Part 2.
# posted by Jesse Wilson
on Tuesday, May 19, 2009
1 comments
post a comment
My perspective on Atinject
Back in February, a discussion
flared up over Guice's lack of support for industry-standard annotations. James Strachan described the problem succinctly:
So my show stopper issue with ever adopting & recommending Guice is to be able to use IoC framework agnostic annotations for lifecycle & injection (JSR 250/EJB3 for starters, @Resource, @PostConstruct, @PreDestroy - you could throw Spring/WebBeans/JAX-RS/JAXWs in there too really but JSR250/EJB3 is the tip of this massive iceberg). i.e. so that my application code can be used by Spring or Guice IoC containers without having to double-annotate everything (using JSR 250 and Guice annotations) or using JSR 250 annotations then forcing producer methods for every class I write to be written by users using Guice.
In response to the immediate issue, Guice got support for
custom injectors. And we're planning an API for lifecycle callbacks.
Unfortunately, the current annotations aren't really sufficient.
@Resource precludes immutability. It lacks a compiler-checked way to qualify a type. And it lacks a mechanism for lazy or multiple injection.
Since @Resource is mostly a non-starter, each of the established containers defines its own annotations:
public class TweetsClient {
@Autowired
@com.google.inject.Inject
@org.apache.tapestry5.ioc.annotations.Inject
public TweetsClient(OpenIdCredentials credentials,
HttpConnectionFactory connectionFactory) {
...
}
}
The differences between these are superficial. There just aren't many ways to mark an injection point!
To unity their annotations, the established Java dependency injection vendors collaborated on a
new API to serve as a common standard. The entire API is five annotations (including
@Inject) plus
Provider for lazy/multiple injection.
The spec enables
class portability. Your classes can be used with any injector: annotate implementation class once to support all of
Spring,
Guice,
PicoContainer,
Tapestry IoC, and
Simject.
But the new standard
does not cover injector configuration. The lack of standardized configuration hurts
application portability, because you must reconfigure for each injector.
Configuration was left out because there's
no consensus on the best way to do it. Unlike annotations, each injector takes a distinct approach, each with relative strengths and weaknesses. For example, consider this (simplified) matrix:
Injector | XML etc. | Code | Annotations | Notes |
---|
Spring | ✔ | optional | optional | |
Guice | | ✔ | ✔ | |
Butterfly | ✔ | | | has its own DSL |
JSR 299 | ✔ | | ✔ | plus classpath scanning |
PicoContainer | | ✔ | ✔ | |
I'm excited about
the new proposal because it's pragmatic. It paves the well-worn paths (the annotations), but permits innovation to foster elsewhere. Fantastic work guys!
# posted by Jesse Wilson
on Thursday, May 07, 2009
3 comments
post a comment
Java Minutiae - Reflex
Pop QuizThe following is a method from a
very decent implementation of Java SE. I've substituted
x and
y for the actual method name and parameter type. What are the values of
x and
y ?
public static boolean x(y z)isNaN(float z) {
return z != z;
}
PS - it's a reasonable method. it may take you a few minutes to come up with the answer.
Show Answerx = isNaN, y = float
It turns out that Float.NaN != Float.NaN
. Weird! The same is true for Double.NAN
. Since they're the only values with this property, testing for a lack of reflexivity uniquely identifies these values.
# posted by Jesse Wilson
on Monday, May 04, 2009
1 comments
post a comment