Contexts and Dependency Injection (CDI) praktycznie - zestawienie środowiska z JBoss Weld, Arquillian i Apache Maven 2

Z Jacek Laskowski - Wiki Projektanta Java EE

Seria "... praktycznie" ma już swoje odbicie w "JPA2 praktycznie" z Java Persistence (JPA) 2.0 praktycznie - zestawienie środowiska z EclipseLink i Apache Maven 2 oraz "SCA praktycznie" z Service Component Architecture (SCA) praktycznie - zestawienie środowiska z Apache Tuscany i Eclipse IAM, a teraz nadeszła kolej na kolejne, dla specyfikacji JSR-299: Contexts and Dependency Injection for the Java EE platform (w skrócie CDI).

Jej poznawaniem zajmuję się na moim blogu w kategorii CDI i tam można znaleźć więcej informacji na poziomie specyfikacyjnym.

Referencyjną implementacją specyfikacji CDI jest JBoss Weld, więc on otwiera serię, z późniejszymi planami podejścia do innych środowisk uruchomieniowych - Java SE vs Java EE czy JBoss Weld vs Apache OpenWebBeans.

Zgodnie ze specyfikacją CDI, środowiskiem uruchomieniowym dla niej jest przede wszystkim serwer aplikacyjny Java EE 6, jednakże same założenia specyfikacji nie są sprzeczne z możliwością uruchomienia CDI w ramach Java SE 5 i wyżej. Jedynym problemem z jakim możemy się zetknąć jest brak standardu na uruchomienie CDI w ramach Java SE, jak to ma miejsce chociażby dla specyfikacji Java Persistence (JPA). Pewnie przyjdzie nam jeszcze poczekać na uregulowanie tej kwestii i upublicznienie wspólnego API, a każde uruchomienie implementacji CDI będzie wymagało specyficznego zestawu wywołań metod.

Weld idzie nam na przeciw i udostępnia przykłady, które można uruchomić na Java SE w katalogu examples/se. W ramach tego katalogu mamy dwa przykłady hello-world oraz numberguess. Wykorzystują one klasę uruchomieniową org.jboss.weld.environment.se.StartMain. Jest to dobre źródło wiedzy na rozpoczęcie poznawania tematu uruchamiania Weld na Java SE.

W tym artykule skorzystam z jeszcze jednego rozwiązania wspierającego testowanie CDI z Weld bez konieczności uruchamiania serwera aplikacyjnego JEE6 o nazwie Arquillian. Z jego pomocą stworzenie testu wymagać będzie jedynie użycia adnotacji @RunWith(Arquillian.class). Doskonałym materiałem wprowadzającym jest angielskojęzyczny artykuł CDI-powered Unit Testing using Arquillian, w którym znalazłem wiele (wszystkie?) potrzebne informacje. Dalsza lektura powinna zawierać podręcznik Arquillian: An integration testing framework for Containers :: Reference Guide

Dla zwrócenia uwagi: serwer aplikacyjny Java EE 6 wspiera CDI i nie trzeba nic dodatkowego instalować. O tym jednak w późniejszym terminie.

Kody źródłowe projektu znajdują się w moim repozytorium Mercuriala na Google Code jako cdi-praktycznie.

Spis treści

Stworzenie struktury projektowej z Apache Maven - mvn archetype:generate

(Byłbym wdzięczny za wskazówki, jak uruchomić poniższe z poziomu Gradle)

Zaczynam typowo poleceniem mvn archetype:generate z opcją -B - utworzenia projektu w trybie wsadowym (nieinteraktywnym).

mvn archetype:generate -B \
   -DarchetypeArtifactId=maven-archetype-quickstart \
   -DgroupId=pl.jaceklaskowski.cdi \
   -DartifactId=cdi-praktycznie \
   -Dversion=1.0

W ten sposób tworzę strukturę projektu cdi-praktycznie i on, od tej pory, staje się katalogiem bieżącym.

Konfiguracja projektu z pomocą IDE - NetBeans IDE 6.9

Otwieramy projekt w wybranym IDE. W moim przypadku będzie to NetBeans IDE 6.9 RC2. Jego zaletą jest wbudowana obsługa projektów mavenowych.

Wczytanie projektu w NetBeans IDE sprowadza się do File | Open Project (alternatywnie Cmd+Shift+O na MacOS).

Następnie należy podnieść konfigurację projektu do wersji Java SE 6, dodać zależności CDI z projektu referencyjnego Weld i wsparcia testowego Arquillian oraz podnieść domyślną wersję JUnit do 4.8.1.

Ostatecznie, plik konfiguracyjny projektu pom.xml prezentuje się następująco:

<?xml version="1.0"  encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>pl.jaceklaskowski.cdi</groupId>
    <artifactId>cdi-praktycznie</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>cdi-praktycznie</name>
    <url>http://www.jaceklaskowski.pl/wiki</url>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <arquillian.version>1.0.0.Alpha2</arquillian.version>
        <junit.version>4.8.1</junit.version>
        <weld.version>1.0.1-Final</weld.version>
    </properties>
    <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>
    <pluginRepositories>
        <pluginRepository>
            <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>
        </pluginRepository>
    </pluginRepositories>
    <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>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-junit</artifactId>
            <version>${arquillian.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-weld-embedded</artifactId>
            <version>${arquillian.version}</version>
            <scope>test</scope>
        </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>
        </dependency>
    </dependencies>
</project>

Stworzenie klasy testowej - pl.jaceklaskowski.cdi.BeanManagerInjectionTest

Tworzę klasę testową pl.jaceklaskowski.cdi.BeanManagerInjectionTest wykorzystującą funkcjonalność CDI ze wsparciem Arquillian w katalogu src/test/java jak niżej:

package pl.jaceklaskowski.cdi;
 
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import junit.framework.TestCase;
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.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.asset.ByteArrayAsset;
import org.junit.Test;
import org.junit.runner.RunWith;
 
@RunWith(Arquillian.class)
public class BeanManagerInjectionTest extends TestCase {
 
    @Deployment
    public static JavaArchive createTestArchive() {
        return ShrinkWrap.create("test.jar", JavaArchive.class).addManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml"));
    }
    @Inject
    BeanManager manager;
 
    @Test
    public void shouldReturnNotNullReferenceOfBeanManager() {
        assertNotNull(manager);
        System.out.println(manager.getClass());
    }
}

Nie wnikając specjalnie w szczegóły działania Arquillian, jego użycie wymaga użycia adnotacji @RunWith(Arquillian.class) oraz określenia struktury paczki do uruchomienia w metodzie oznaczonej adnotacją @org.jboss.arquillian.api.Deployment. Metoda oznaczona tą adnotacją, za pomocą specyficznych dla szkieletu Arquillian obiektów, konstruuje uruchomieniową strukturę paczki CDI. W powyższym przykładzie statyczna metoda public static JavaArchive createTestArchive() konstruuje archiwum test.jar z pojedynczym, pustym plikiem beans.xml. Tyle nam wystarczy do wzbudzenia infrastruktury CDI, tj. JBoss Weld uruchamianego na poziomie Java SE.

Uruchomienie projektu

Uruchomienie w NetBeans IDE sprowadza się do wybrania menu Test pod prawym przyciskiem myszki na projekcie w widoku Projects (można również Cmd+F6 na MacOS).

Plik:cdi-praktycznie-netbeans-testresults.png

Zielono, czyli działa! W ten sposób przygotowałem sobie środowisko do dalszej nauki CDI.

Dla wytrwałych: uruchomienie testowania pojedynczej klasy testowej w Maven sprowadza się do polecenia mvn -Dtest=BeanManagerInjectionTest test.

devmac:cdi-praktycznie jacek$ mvn -Dtest=BeanManagerInjectionTest test
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building cdi-praktycznie
[INFO]    task-segment: [test]
[INFO] ------------------------------------------------------------------------
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.cdi.BeanManagerInjectionTest
62 [main] INFO org.jboss.weld.Version - WELD-000900 1.0.1 (Final)
78 [main] INFO org.jboss.weld.Bootstrap - WELD-000101 Transactional services not available. Injection of @Inject UserTransaction not available.
Transactional observers will be invoked synchronously.
471 [main] WARN org.jboss.interceptor.model.InterceptionTypeRegistry - Class 'javax.ejb.PostActivate' not found, interception based on it is not enabled
471 [main] WARN org.jboss.interceptor.model.InterceptionTypeRegistry - Class 'javax.ejb.PrePassivate' not found, interception based on it is not enabled
class org.jboss.weld.manager.BeanManagerImpl
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.976 sec
 
Results :
 
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Osobiste