Mechanizm zdarzeń w CDI z JBoss Weld, Arquillian i Apache Maven
Z Jacek Laskowski - Wiki Projektanta Java EE
Pojawiła się potrzeba rozpracowania tematu obsługi zdarzeń w JSR-299: Contexts and Dependency Injection for the Java EE platform (w skrócie CDI). Jako to u mnie bywa, teoria musi być utrwalona praktyką i potrzebowałem zestawić właściwe środowisko uruchomieniowe. Wcześniejsze doświadczenia z CDI wykonałem z referencyjną implementacją JBoss Weld, które spisałem w artykule Contexts and Dependency Injection (CDI) praktycznie - zestawienie środowiska z JBoss Weld, Arquillian i Apache Maven 2, więc nie trwało długo, zanim zdecydowałem się na ponowne spotkanie z Weld.
Jako materiał teoretyczny posłużył mi rozdział 10. Events specyfikacji CDI.
W przeciwieństwie do poprzedniego artykułu, w którym pracowałem z NetBeans IDE, w tym użyłem Eclipse IDE (Helios) 3.6.2 (w wydaniu rozszerzonym o obsługę JEE - Eclipse IDE for Java EE Developers, aczkolwiek jej użycie, to efekt "już ją po prostu miałem").
Skoro w użyciu mam Apache Maven i Eclipse, to ich naturalnym połączeniem jest rozszerzenie Eclipse Integration for Eclipse (instalacja sprowadza się do wybrania menu Help/Eclipse Marketplace... i wybrania Install na liście wyszukanych pakietów).
Z tak zestawionym środowiskiem rozpocząłem rozpoznanie - nic tak przecież nie utrwala świeżo zdobytej wiedzy, jak jej użycie praktyczne.
Kody źródłowe projektu cdi-events dostępne są jako cdi-events.zip.
Stworzenie projektu cdi-events
Skorzystam ze wsparcia Eclipse IDE i rozszerzenia Eclipse Integration for Eclipse do stworzenia projektu cdi-events.
Wybieram File > New > Other... (lub krócej Cmd+N) i w polu Wizards wpisuję maven.
Później podaję dane projektu i wciskam Finish (w razie wątpliwości, uprasza się o kontakt z autorem).
Należy wprowadzić kilka zmian związanych z wersją Java SE 6 oraz zależności i inne konfiguracje projektowe, aby ostatecznie plik konfiguracyjny projektu pom.xml zawierał następującą treść.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>pl.japila.cdi</groupId> <artifactId>cdi-events</artifactId> <version>0.0.1-SNAPSHOT</version> <name>CDI :: Chapter 10. Events</name> <url>http://www.jaceklaskowski.pl/wiki</url> <properties> <arquillian.version>1.0.0.Alpha5</arquillian.version> <junit.version>4.8.2</junit.version> <weld.version>1.1.0.Final</weld.version> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public/</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </snapshots> </repository> </repositories> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.weld</groupId> <artifactId>weld-core-bom</artifactId> <version>${weld.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-junit</artifactId> <version>${arquillian.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-weld-se-embedded-1.1</artifactId> <version>${arquillian.version}</version> </dependency> <dependency> <groupId>org.jboss.weld</groupId> <artifactId>weld-core</artifactId> </dependency> <dependency> <groupId>org.jboss.weld</groupId> <artifactId>weld-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>2.2</version> </dependency> </dependencies> </project>
Stworzenie klasy ziarna zarządzanego - pl.japila.cdi.EnabledBean
W 10.1. Event types and qualifier types (strona 71) specyfikacji CDI czytamy:
"An event object is an instance of a concrete Java class with no type variables."
Oznacza to, że dowolna klasa javowa, która nie jest typem generycznym, może być typem komunikatu, który z kolei będzie nośnikiem rozsyłanej informacji. Treść rozsyłanego komunikatu zawarta będzie w stanie obiektu dowolnego, przez nas wybranego, typu. W szczególności może to być dowolna istniejąca klasa z Java SE, np. java.lang.String.
W 10.2. Observer resolution (strona 71) specyfikacji CDI czytamy:
"An event is delivered to an observer method if:
- The observer method belongs to an enabled bean."
Mnie zastanowiło, czym może być an enabled bean. Odpowiedź znalazłem w 3.1. Managed beans (strona 21), w którym czytamy, że dowolna klasa javowa (bez udziwnień - tutaj liczę na intuicję) jest klasą ziarna zarządzanego. Łatwo i przyjemnie, aż trudno uwierzyć, że to się dzieje w ramach specyfikacji Java EE (!)
Pozostało rozpoznać jak owa metoda przechwytująca miałaby wyglądać. Odpowiedź jest w 10.4.2. Declaring an observer method (strona 74):
"An observer method may be declared by annotating a parameter @javax.enterprise.event.Observes."
Z tą wiedzą stworzyłem poniższą klasę zarządzaną. Zwróć uwagę na użycie wspomnianej adnotacji @Observes przy parametrze wejściowym firstName metody observerMethod.
package pl.japila.cdi; import javax.enterprise.event.Observes; public class EnabledBean { public void observerMethod(@Observes String firstName) { System.out.println("Observed firstName: " + firstName); } }
Sygnatura metody nie jest określona przez specyfikację, za wyjątkiem użycia @Observes i kilku innych wyjątków związanych z CDI. Innymi słowy, jeśli tylko trzymamy się zasad rządzących językiem Java, jesteśmy zgodni.
Klasę zapisujemy jako EnabledBean.java w katalogu src/main/java/pl/japila/cdi.
Stworzenie klasy testowej - pl.japila.cdi.EventsTest
W 10.3. Firing events (strona 73) czytamy:
"Beans fire events via an instance of the javax.enterprise.event.Event interface, which may be injected."
Klasa kliencka - nadawcza - korzysta z przekazanego (wstrzelonego) obiektu realizującego interfejs javax.enterprise.event.Event, którego typem jest typ komunikatu i przez metodę javax.enterprise.event.Event.fire(Event) wysyła komunikat.
Możemy również skorzystać z przekazanego obiektu manager typu javax.enterprise.inject.spi.BeanManager, aczkolwiek ten sposób nazwałbym niskopoziomowym i dedykowanym dla rozszerzeń CDI. Metoda javax.enterprise.inject.spi.BeanManager.fireEvent(Object, Annotation) jest mechanizmem nadania komunikatu przez "szynę" CDI.
Dociekliwi z pewnością zauważą podobieństwo mechanizmu komunikatów w CDI do wzorca komunikacyjnego publish/subscribe, w którym jeden komunikat może być dostarczony do wielu odbiorców (w szczególności do żadnego).
package pl.japila.cdi; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import java.lang.annotation.Annotation; import javax.enterprise.event.Event; import javax.enterprise.inject.Any; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import org.jboss.arquillian.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ArchivePaths; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) public class EventsTest { @Deployment public static JavaArchive createTestArchive() { return ShrinkWrap.create(JavaArchive.class, "test.jar") .addPackage(EnabledBean.class.getPackage()) .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); } @Inject BeanManager manager; @Inject @Any Event<String> firstNameEvent; @Test public void shouldReturnNotNullReferenceOfBeanManager() { assertNotNull(manager); assertThat(manager.getClass().getName(), is("org.jboss.weld.manager.BeanManagerImpl")); // wysłanie komunikatu właściwie - korzystając z "klienckiego" API firstNameEvent.fire("Jacek"); // wysłanie komunikatu niskopoziomowo - jedynie dla zaawansowanych manager.fireEvent("Agata", new Annotation[0]); } }
Klasę zapisujemy jako EventsTest.java w katalogu src/test/java/pl/japila/cdi.
Uruchomienie pozostawiam najbardziej wytrwałym. Uprasza się o kontakt z autorem celem wyjaśnienia szczegółów w razie problemów lub gdyby pojawiła się potrzeba uzupełnienia treści artykułu. Powodzenia!

