During these (much deserved) vacations, I worked on a pet project of mine which uses CDI with the Weld implementation and SLF4J with the Logback implementation.
The terms of the problem were very simple: Iwanted the logs of my application to be displayed in a Swing table, i.e. a Logback appender had to write in a table. The table was managed in CDI but the appender was not: this was no surprise since many older-generation frameworks have a lifecycle management on their own. Prior-to-JEE6 Servlets, Log4J appenders, Struts actions all have their own lifecycle management, completely unrelated to CDI. Worse, dependency injection frameworks are in large part incompatible with one another (Spring comes to mind) and need an adapter to bridge between their contexts, so this use-case is a common one.
My first lead was to bootstrap Logback myself but since Weld uses SLF4J under the cover, I found no way. After some time, the conclusion was that CDI provided no means to inject my managed table into the Logback appender. That was a sad conclusion, but I didn’t stop there: when the specification doesn’t address your needs, jump to the implementation (but with remorse…).
There’s a nice complementary module to Weld called Weld Extensions, that provide additional features not mentioned in the JSR.
Note that Weld Extensions seems to have been replaced by Seam Solder, but the logic stays the same:
obtain a reference to the BeanManager
and forcibly inject the table in the appender through it.
In Extension, the callback is made through the Extension
marker interface.
Such extensions are looked up by Weld using the service provider mechanism.
If you’re not familiar with it, have a look at it, it’s elegant and useful in many use-cases.
So, I created my META-INF/services/javax.enterprise.inject.spi.Extension file.
Now, let’s have a look at the extension itself:
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import org.jboss.weld.environment.se.events.ContainerInitialized;
import org.slf4j.impl.StaticLoggerBinder;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
public class LogbackWeldExtension implements Extension { (1)
public void bind(@Observes ContainerInitialized event, BeanManager manager) { (2)
LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory();
List loggers = loggerContext.getLoggerList();
for (Logger logger : loggers) {
Appender appender = logger.getAppender("Table"); (3)
if (appender != null) {
AnnotatedType type = manager.createAnnotatedType(appender.getClass());
InjectionTarget target = manager.createInjectionTarget(type);
CreationalContext creationalContext = manager.createCreationalContext(null); (4)
target.inject(appender, creationalContext);
}
}
}
}
1 | we inherit from Extension |
2 | we observe the container initialized event, meaning the method is called when the Weld context is initialized.
Note the BeanManager is the second parameter and is injected automatically by the framework! |
3 | we use Logback’s API to get the handle on the unmanaged appender. Note that we get the reference by name, which I did because I was lazy at the time… The future version will browse through all appenders and get the correct one by class name. |
4 | we use Weld’s API to inject the unmanaged logger into the beans that needs it. Note there’s no reference to the injected table bean, it’s just standard DI if the managed beans are correctly annotated. If you use this code, also note the compiler will warn you about the unused generics (those are a pain to work with!). |
That’s it!