Wprowadzenie do JEE6

post_img

Specyfikacja JEE6 została opublikowana pod koniec 2009 roku. Obecnie jest wspierana przez wszystkie liczące się serwery aplikacyjne. Specyfikacja nie jest rewolucją w JEE tak jak miało to miejsce w przypadku wydania specyfikacji JEE5 – jest raczej bardzo przemyślaną ewolucją i kontynuacją zmian mających na celu uproszczenie wytwarzania aplikacji. W specyfikacji pojawiły się między innymi JSF 2.0, Servlets 3.0, JPA 2.0, EJB 3.1, Bean Validation oraz CDI (Context and Dependency Injection).

W niniejszym artykule chciałbym pokazać jak stworzyć prostą aplikację webową w Javie EE 6 wykorzystując w warstwie prezentacji JSF 2.0 z CDI wspartą session beanem zgodnym z EJB 3.1 oraz JPA.

Co potrzebujemy

Aby nasza prosta aplikacja webowa zadziałała utworzymy war’a zawierającego:

  • szablon stron JSF
  • konkretną stronę JSF opartą na w/w szablonie
  • model oraz logikę warstwy prezentacji (w formie Managed Bean’ów JSF)
  • logikę biznesową w postaci Stateless Session Bean
  • deskryptor aplikacji webowej – web.xml
  • konfigurację persystencji – persistence.xml

Struktura projektu

Zacznijmy od utworzenia projektu. Chciałbym zwrócić szczególną uwagę na prostotę JEE6 i proponuję zacząć od pustego projektu mavenowego. Wykorzystamy w tym celu archetyp maven-archetype-quickstart. Możemy zrobić to z konsoli lub z naszego ulubionego IDE. Z linii poleceń powinno to wyglądać mniej więcej tak:

mvn archetype:generate
wybieramy maven-archetype-quickstart
groupId=pl.atena
artifactId=jee6-intro
version=1.0-SNAPSHOT

Natomiast w Eclipsie z użyciem wtyczki m2 w następujący sposób:

Po wygenerowaniu projektu jego struktura powinna wyglądać tak:

Zmodyfikujmy strukturę naszego projektu, tak aby odpowiadała ona wymaganej strukturze aplikacji webowej. W tym celu dodajemy katalog src/main/webapp i w nim katalogi resources oraz WEB-INF. W WEB-INF przygotowujemy katalog na template’y (templates). Nasz projekt powinien teraz wyglądać następująco:

Zależności i budowanie projektu

Nasz projekt będzie budowany przy użyciu maven’a. JEE6 umożliwia spakowanie całej aplikacji razem z komponentami EJB do pliku war. Ustawiamy więc w pliku pom.xml packaging na war. Dodajemy też zależność od javaee-api w wersji 6.0. Nasz pom.xml powinien wyglądać tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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.atena</groupId>
  <artifactId>jee6-intro</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>jee6-intro</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>6.0</version>
    </dependency>
  </dependencies>
</project>

Kodujemy

Teraz czas coś zakodować. Ze względu na to, że najbardziej istotne zmiany w JEE6 dotyczą warstwy prezentacji chciałbym nietypowo zacząć od widoku – przygotujemy szablon strony w katalogu src/main/webapp/WEB-INF/templates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    <title>Java EE Sample Application</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <h:outputStylesheet name="css/style.css" />
</h:head>
<h:body>
    <div id="container">
        <div class="header" id="header">Welcome to JEE6 application!</div>
        <div class="content" id="content">
            <ui:insert name="content"></ui:insert>
        </div>
        <div class="footer" id="footer">Footer</div>
    </div>
</h:body>
</html>

W katalogu webapp tworzymy pierwszą stronę (index.html):

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    template="/WEB-INF/templates/default.xhtml">
    <ui:define name="content">
        <h1>JEE6 Sample!</h1>
    </ui:define>
</ui:composition>

I przygotowujemy deskryptor aplikacji webowej (web.xml), w którym mapujemy wszystkie adresy URL kończące się na jsf na servlet FacesServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
     http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">


    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>facelets.DEVELOPMENT</param-name>
        <param-value>true</param-value>
    </context-param>
</web-app>

Możemy teraz spakować naszą aplikację i zdeployować na serwerze aplikacji. W tym celu robimy mvn package i wygenerowanego war’a umieszczamy na wybranym serwerze aplikacyjnym (w przypadku JBoss’a wrzucamy go do katalogu deploy). Po wgraniu wchodzimy na stronę http://localhost:8080/jee6-intro-0.0.1-SNAPSHOT/index.jsf. Jak widać w odpowiedzi dostaliśmy stronę na bazie szablonu default.xhtml wypełnioną treścią z pliku index.html.

Co prawda mało tu jeszcze JEE6, ale mamy solidną podstawę pod dalszy development.

Czas na trochę magii…

Każdy kto popisał trochę w JSF 1.2 na pewno z przykrością wspomina utrzymywanie pliku faces-config.xml. Wraz z pojawieniem się JSF 2.0 oraz CDI możemy korzystać z adnotacji. Stwórzmy zatem pierwszego managed bean’a oraz klasę reprezentującą model.

Będzie to pierwszy przykład użycia CDI – aby jednak CDI w ogóle zadziałał musimy stworzyć praktycznie pusty plik beans.xml w katalogu WEB-INF:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
     http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

   <alternatives/>
   <decorators/>
   <interceptors/>
</beans>

Jest to informacja dla serwera aplikacyjnego, że wykorzystujemy CDI w projekcie.

Dalszy development zaczniemy od modelu – stwórzmy klasę reprezentującą klienta i oznaczmy ją adnotacją @Model. Jest to zwykła klasa (POJO). Adnotacja @Model robi z niej jednak klasę zarządzaną przez kontener i udostępnia ją do użycia za pomocą EL nadając jej nazwę odpowiadającą nazwie klasy, ale pisaną małą literą. Przy okazji tworzenia klasy modelu zdefiniujemy też constrainty walidacyjne, które zostaną użyte do walidacji formularza.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package pl.atena.jee6;

import javax.enterprise.inject.Model;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;

@Model
public class Client {

    @NotNull
    private String name = "John";
   
    @NotNull
    private String surname = "Smith";
   
    @Max(value = 250)
    private Integer height = 185;

    public Client() {}
   
    public Client(String name, String surname, Integer height) {
        super();
        this.name = name;
        this.surname = surname;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

}

Obiekt tej klasy możemy od razu używać w warstwie widoku korzystając z EL. Poniższa linijka dodana do pliku index.xhtml spowoduje wypisanie domyślnych wartości atrybutów ustawionych w klasie Client:

1
#{client.name} #{client.surname} #{client.height}

Mając gotowy model, możemy rozpocząć implementację logiki warstwy prezentacji. Umieścimy ją w Managed Beanie, którego nazwiemy ClientController. Managed Bean w JSF 2.0 to zwykła klasa POJO adnotowana @Named lub @ManagedBean (@ManagedBean używamy gdy mamy aplikację używającą JSF 2.0, ale bez CDI; w przypadku, gdy używamy CDI powinniśmy posługiwać się adnotacją @Named). Poniżej przykładowa implementacja kontrolera – jest to oczywiście póki co zaślepka, ale pozwoli nam na sprawdzenie jak z widoku zawołać metody wystawione w Managed Beanie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package pl.atena.jee6;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Named;

@Named
public class ClientController {

    public List<Client> getClients() {
        System.out.println("getClients executed");
        List<Client> clients = new ArrayList<Client>();
        clients.add(new Client("John", "Smith", 180));
        clients.add(new Client("Aleksander", "Pete", 190));
        clients.add(new Client("Karol", "Adwin", 170));
        return clients;
    }
   
    public void saveClient(Client client) {
        System.out.println("saveClient executed,
                client name = "
+ client.getName());
    }
   
}

Mając tak zdefiniowanego Managed Beana możemy zawołać metody getClients i saveClient z poziomu widoku (index.xhtml) w następujący sposób:

1
2
#{clientController.getClients()}
#{clientController.saveClient(client)}

Przykład wyświetlenia danych klientów w tabelce:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h:dataTable var="clientIter" value="#{clientController.getClients()}">
    <h:column>
        <f:facet name="header">name</f:facet>
            #{clientIter.name}
        </h:column>
        <h:column>
        <f:facet name="header">surname</f:facet>
                #{clientIter.surname}
        </h:column>
        <h:column>
        <f:facet name="header">height</f:facet>
            #{clientIter.height}
        </h:column>
</h:dataTable>

Oraz formularza do wprowadzenia danych wraz z tagami wyświetlającymi błędy walidacji:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h:form>
    <h:outputLabel value="name:" /><br/>
    <h:inputText id="name" value="#{client.name}" />
    <h:message for="name" errorClass="invalid"/><br/><br/>
       
    <h:outputLabel value="surname:" /><br/>
    <h:inputText id="surname" value="#{client.surname}" />
    <h:message for="surname" errorClass="invalid"/><br/><br/>
       
    <h:outputLabel value="height:" /><br/>
    <h:inputText id="height" value="#{client.height}" />
    <h:message for="height" errorClass="invalid"/><br/><br/>
       
    <h:commandButton action="#{clientController.saveClient(client)}" value="save"/>
</h:form>

Warto wspomnieć, że obiekt klasy adnotowanej @Named jest zarządzany przez kontener. Oznacza to między innymi, że można do niego wstrzykiwać komponenty EJB za pomocą adnotacji @EJB.

Na koniec naszych ćwiczeń spróbujemy zapisać coś do bazy – w tym celu zmienimy naszą klasę Client w encję JPA, stworzymy prostego EJB z metodami saveClient i getClients, przerobimy ClientController tak, aby korzystał z nowego EJB i dodamy plik persistence.xml konfigurujący JPA.

Nasza klasa Client staje się encją JPA – zamieniamy adnotację @Model na @Entity oraz dodajemy generowany id – klasa przyjmuje teraz następującą postać:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package pl.atena.jee6;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;

@Entity
public class Client {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    private String name;

    @NotNull
    private String surname;

    @Max(value = 250)
    private Integer height;

    public Client() {
    }

    public Client(String name, String surname, Integer height) {
        super();
        this.name = name;
        this.surname = surname;
        this.height = height;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

}

Tworzymy komponent EJB ClientService, który korzystając z EntityManagera zapewni dostęp do bazy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package pl.atena.jee6;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class ClientService {

    @PersistenceContext(name="jee6-intro")
    EntityManager entityManager;
   
    public List<Client> getClients() {
        return entityManager.createQuery("select c from Client c").getResultList();
    }
   
    public void saveClient(Client client) {
        entityManager.persist(client);
    }
   
}

oraz modyfikujemy klasę ClientController tak, aby korzystając z EJB ClientService realizowała operacje save i get – klasa wygląda teraz następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package pl.atena.jee6;

import java.util.List;

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;

@Named
@RequestScoped
public class ClientController {

    @EJB
    ClientService clientService;
   
    Client client = new Client();
   
    @Produces @Named
    public Client getClient() {
        return client;
    }
   
    public List<Client> getClients() {
        return clientService.getClients();
    }
   
    public void saveClient(Client client) {
        clientService.saveClient(client);
    }
   
}

Warto zwrócić uwagę na metodę getClient adnotowaną @Produces i @Named – ponieważ klasa Client nie jest już modelem JSF tylko encją nie jest ona wprost dostępna w faceletach. Metoda getClient po oznaczeniu @Produces @Named stała się fabryką dla obiektu klasy Client.

Na koniec dodajemy jeszcze persistence.xml do katalogu src/main/resources/META-INF. Nasz persistence.xml zakłada użycie domyślnego datasource’a dostępnego w JBoss7. Wskazaliśmy też, że chcemy aby schemat bazy danych został wygenerowany podczas deployu aplikacji.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
  xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
     http://java.sun.com/xml/ns/persistence
     http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

   <persistence-unit name="jee6-intro">
      <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
      <properties>
         <!-- Properties for Hibernate (default provider for JBoss AS) -->
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
         <property name="hibernate.show_sql" value="true"/>
         <property name="hibernate.transaction.flush_before_completion" value="true"/>
         <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
      </properties>
   </persistence-unit>
</persistence>

Ostatecznie nasz projekt powinien mieć strukturę taką, jak na poniższym zrzucie ekranu:

Wynikiem działania będzie strona www przypominająca poniższą:

Podsumowanie

W tych kilku linijkach kodu udało się stworzyć prostą aplikację webową wykorzystującą JSF 2.0, CDI, EJB 3.1, JPA, Bean Validation oraz facelety z EL i szablonem stron zapakowaną w war’a. JEE jeszcze naprawdę nigdy nie było tak proste.

Nasza przykładowa aplikacja do pobrania: jee6-intro.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *