Guice ist ein leichtgewichtiges DI-Framework von Google, das ich, wie bereits in einigen Artikeln erwähnt, gern an Stelle von Spring einsetze, wenn ich mit einem Container ohne CDI-Support arbeite, zum Beispiel JBoss 5.1 oder Java SE. Guice unterstützt JSR-330 (Dependency Injection), leider jedoch nicht JSR-250 (Common Annotations).
Das ist schade, denn gerade die @PostConstruct-Annotation wäre sehr nützlich beim Umgang mit Guice, ermöglicht sie doch auf elegante Art und Weise, die Lücke zwischen field-based-Injection und constructor-based-Injection zu schließen. Es gibt mit dem inzwischen veraltetem GuicyFruits und dem aktuellen MycillaGuice Open Source Ergänzungen, die diese Schwäche von Guice kompensieren.
Wenn es allerdings nur darum geht, kontrolliert @PostConstruct-Initialisierung zu betreiben, muss nicht gleich mit Kanonen auf Spatzen geschossen werden – in diesem Fall tut es auch ein einfaches eigenes Modul, das sich als TypeListener in den Lifecycle von Guice einklinkt.
public enum PostConstructModule implements Module, TypeListener { INSTANCE; /** * {@inheritDoc} * * @see com.google.inject.Module#configure(com.google.inject.Binder) */ @Override public void configure(final Binder binder) { // an alle Klassen diesen Listener binden binder.bindListener(Matchers.any(), this); } /** * Ruft nach der Injection die Postconstruct Methode(n) auf, wenn sie existieren. * * <p> * {@inheritDoc} * * @see com.google.inject.spi.TypeListener#hear(com.google.inject.TypeLiteral, com.google.inject.spi.TypeEncounter) */ @Override public <I> void hear(final TypeLiteral<I> type, final TypeEncounter<I> encounter) { encounter.register(new InjectionListener<I>() { @Override public void afterInjection(final I injectee) { // alle postconstruct Methoden (nie null) ausführen. for (final Method postConstructMethod : filter(asList(injectee.getClass().getMethods()), MethodPredicate.VALID_POSTCONSTRUCT)) { try { postConstructMethod.invoke(injectee); } catch (final Exception e) { throw new RuntimeException(format("@PostConstruct %s", postConstructMethod), e); } } } }); } }
Das Modul wird als Enum implementiert (Enum-Singleton-Pattern). Die configure()-Methode sorgt dafür, dass potentiell jedes initialisierte Objekt (any()) auf vorhandene @PostConstruct-Methoden geprüft wird. Dies geschieht im gebundenen TypeListener, der in der Methode hear() bzw. afterInjection() implementiert wird.
Die afterInjection()-Methode bekommt das gerade per Guice erzeugte Objekt übergeben und ruft alle (typischerweise eine) annotierten Methoden auf. Das Hilfs-Predicate „MethodPredicate.VALID_POSTCONSTRUCT“ sorgt dabei dafür, dass nur Methoden berücksichtigt werden, die annotiert, public, void und parameterlos sind.
Dieses Modul kann nun per install() bei der Erzeugung von Injector-Objekten eingebunden werden:
public class PostConstructModuleTest extends AbstractModule { @Inject private Injector injector; public static class A { private String name = "foo"; @PostConstruct public void init() { name = "bar"; } } @Before public void setUp() { createInjector(this).injectMembers(this); } @Override protected void configure() { install(PostConstructModule.INSTANCE); } @Test public void shouldPostConstructNameBar() { assertThat(injector.getInstance(A.class).name, is("bar")); } }
Per setup() und configure() installiert dieser Test das PostConstructModule und nutzt die so erzeugte Injector-Instanz, um in der Test-Methode sicherzustellen, dass das vorbelegte Attribut „name“ per @PostConstruct überschrieben wird.
Die hier verwendeten Klassen können hier heruntergeladen werden. Das Projekt benötigt darüber hinaus Guice und Guava.