|
CustomInjections
Performing custom injections with type and injection listeners
Custom InjectionsIn addition to the standard @Inject-driven injections, Guice includes hooks for custom injections. This enables Guice to host other frameworks that have their own injection semantics or annotations. Most developers won't use custom injections directly; but they may see their use in extensions and third-party libraries. Each custom injection requires a type listener, an injection listener, and registration of each. TypeListeners get notified of the types that Guice injects. Since type listeners are only notified once-per-type, they are the best place to run potentially slow operations, such as enumerating a type's members. With their inspection complete, type listeners may register instance listeners for values as they're injected. MembersInjectors and InjectionListeners can be used to receive a callback after Guice has injected an instance. The instance is first injected by Guice, then by the custom members injectors, and finally the injection listeners are notified. Since they're notified once-per-instance, these should execute as quickly as possible. Example: Injecting a Log4J LoggerGuice includes built-in support for injecting java.util.Logger instances that are named using the type of the injected instance. With the type listener API, you can inject a org.apache.log4j.Logger with the same high-fidelity naming. We'll be injecting fields of this format: import org.apache.log4j.Logger;
import com.publicobject.log4j.InjectLogger;
public class PaymentService {
@InjectLogger Logger logger;
...
}We start by registering our custom type listener Log4JTypeListener in a module. We use a matcher to select which types we listen for: @Override protected void configure() {
bindListener(Matchers.any(), new Log4JTypeListener());
}You can implement the TypeListener to scan through a type's fields, looking for Log4J loggers. For each logger field that's encountered, we register a Log4JMembersInjector on the passed-in TypeEncounter: class Log4JTypeListener implements TypeListener {
public <T> void hear(TypeLiteral<T> typeLiteral, TypeEncounter<T> typeEncounter) {
for (Field field : typeLiteral.getRawType().getDeclaredFields()) {
if (field.getType() == Logger.class
&& field.isAnnotationPresent(InjectLogger.class)) {
typeEncounter.register(new Log4JMembersInjector<T>(field));
}
}
}
}Finally, we implement the Log4JMembersInjector to set the logger. In this examle, we always set the field to the same instance. In your application you may need to compute a value or request one from a provider. class Log4JMembersInjector<T> implements MembersInjector<T> {
private final Field field;
private final Logger logger;
Log4JMembersInjector(Field field) {
this.field = field;
this.logger = Logger.getLogger(field.getDeclaringClass());
field.setAccessible(true);
}
public void injectMembers(T t) {
try {
field.set(t, logger);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
|
Very interesting! I would like to have my logger being static members of the class, is that possible using something similar? So, how could I inject a static logger using Guice? When I try it (same code but static logger), the field is not injected and I get a NPE when first using the logger. Thanks, Bernard.
Your static field has to be non final which sucks. I added my logger declaration to my class template in eclipse, so every time i create a new class my logger is already there.
We now standardize on slf4j which works great. We can switch at any time by changing a dependency.
I agree though, that adding the declaration in Eclipse is a great idea.
I may have missed this or simply failed my searches, but: Is there any way of performing the above using a standardized @Inject annotation? That is, as far as I understand, guice only calls the MembersInjector?.injectMembers after it's own injection, thus failing (since in this example, Log4j's logger does not have a constructor that works).
So I guess what I'm asking is, can I intercept and replace a guice injection?
That way you could do custom injects without the need of specifying your own annotations.