Blog

@PostConstruct mit Guice

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.

Über den Autor

Jan Galinski

Jan Galinski ist Senior Consultant bei der Holisticon AG und seit vielen Jahren als Architekt und Entwickler in agilen Kundenprojekten unterwegs. Er ist ein leidenschaftlicher Prozessautomatisierer und BPM-Craftsman, am liebsten mit Camunda BPM. Als Contributor zu zahlreichen Open Source Projekten aus den Bereichen BPM und JEE gibt er seine Erfahrung und Wissen gerne weiter. 2016 wurde er mit dem Camunda Community Award ausgezeichnet.

Antwort hinterlassen