I experimented with Google Guice Custom Injections because I wanted to inject a Logger to my guicified application. There is a great part in the Guice documentation about Custom Logger Injection, but in my case I wanted a SLF4J Logger with Logback as implementation. The example in the Guice Wiki is with Log4j Injection.
Here is how I wanted to inject loggers:
1 2 3 4 5 6 | @Log Logger log; public void doSomething{ log.info("Logmessage"); } |
To implement this, we have to create 2 classes and 1 interface. The last step is to register the custom injection in our Guice Module.
Create an Annotation
The first thing we have to do is creating an Annotation which we would like to use for injecting our logger. This is a standard Java Annotation, so this should be pretty straightforward.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import javax.inject.Scope; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Scope @Documented @Retention(RUNTIME) @Target(FIELD) public @interface Log { } |
Note, that we have to annotate our Annotation with javax.inject.Scope to enable the Guice magic and the Annotations have to be present at runtime. Also we restrict the use of the Annotation to Fields.
Create a custom Members Injector
The next part is to create a custom MembersInjector. This is the place where our actual injection occurs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import com.google.inject.MembersInjector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; public class SLF4JMembersInjector<T> implements MembersInjector<T> { private final Field field; private final Logger logger; public SLF4JMembersInjector(Field field) { this.field = field; this.logger = LoggerFactory.getLogger(field.getDeclaringClass()); field.setAccessible(true); } public void injectMembers(T t) { try { field.set(t, logger); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } |
The constructor expects a Field. We need the Field, because our Logger should be the corresponding logger for the actual class. We can ask the Field to which Class it belongs. We also have to set the Field accessible, because we replace the field value later manually in the injectMembers() method. In this method we replace the fields value for a given Object with the logger.
Create a custom TypeListener
The last class we have to implement is a TypeListener class. A TypeListener is required, because we have to tell guice when to inject our Logger. In our case, we want to inject the Logger when a (Logger) Field is annotated with @Log.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import project.annotations.Log; import project.injectors.SLF4JMembersInjector; import com.google.inject.TypeLiteral; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import org.slf4j.Logger; import java.lang.reflect.Field; public class SLF4JTypeListener 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(Log.class)) { typeEncounter.register(new SLF4JMembersInjector<T>(field)); } } } } |
We iterate over every field for an object and check if it wants an Logger injected. This is the case, when there is a org.slf4j.Logger member annotated with our Custom Log Annotation. If we found that, we just register our SLF4JMembersInjector.
Register the TypeListener
The last step we have to do is registering our TypeListener in one of the Guice Modules for the application.
1 2 3 4 5 6 7 8 9 10 11 | import project.typelisteners.SLF4JTypeListener; import com.google.inject.AbstractModule; import com.google.inject.matcher.Matchers; public class Module extends AbstractModule { @Override protected void configure() { bindListener(Matchers.any(), new SLF4JTypeListener()); } } |
Pingback: Creating a Java project with dependency injection, testing, and logging support in eclipse | Manuel Martín
Pingback: Creating a Java project with dependency injection, testing, and logging support in eclipse | Manuel Martín
Thank you!
You might also want to check out https://github.com/raner/loginject for context-specific logger injection (logger-agnostic; supports Guice, HK2, and Dagger DI).