Tworzenie aplikacji Java EE 5 z NetBeans IDE 6.0 i GlassFish v2
Z Jacek Laskowski - Wiki Projektanta Java EE
Poprzedni artykuł Tworzenie aplikacji EJB 3.0 z GlassFish v2, Apache Maven 2 i NetBeans IDE 6.0 przedstawił proces tworzenia aplikacji EJB 3.0 z kilkoma narzędziami wspierającymi tworzenie oprogramowania zgodnego ze specyfikacją Korporacyjnej Javy (Java EE 5). Artykuł uzmysłowił mi potrzebę skrócenia czasu tworzenia aplikacji korporacyjnej do niezbędnego minimum. Postanowiłem iść dalej i spróbować stworzyć aplikację Java EE korzystając wyłącznie z pomocników (ang. wizards) dostarczanych przez NetBeans IDE 6.0. Jako aplikację do utworzenia wybrałem książkę telefoniczną bazując na przykładzie zaczerpniętym z dokumentacji serwera aplikacyjnego Apache Geronimo - Very simple Entity EJB example. Celem było zminimalizowanie koniecznych modyfikacji wykonywanych manualnie na rzecz wykorzystania możliwości środowiska graficznego NetBeans IDE 6.0 oraz technologiczne uatrakcyjnienie przykładu opisanego na wspomnianej stronie (co powinno zostać odzwierciedlone również na niej samej w kontekście użycia Apache Geronimo).
Nazewnictwo poszczególnych klas zostało zaczerpnięte z Wikipedii pod pozycją Telephone directory.
Oprogramowanie
Środowisko składa się z następujących narzędzi:
- GlassFish v2 b58d
- NetBeans IDE 6.0 Nightly z dnia 05.09.2007
Zakłada się, że powyższe oprogramowanie jest zainstalowane i działa poprawnie. Instalacja oprogramowania sprowadza się do pobrania i rozpakowania paczek w wybranym katalogu.
Kompletny projekt do zaimportowania do NetBeans IDE 6.0 dostępny jest jako javaee-phonebook.zip.
Utworzenie projektu aplikacji korporacyjnej - Phonebook
Rozpoczynamy od utworzenia nowego projektu aplikacji korporacyjnej korzystając z menu File > New Project... (bądź alternatywnie: Ctrl+Shift+N) i wybieramy kategorię Enterprise, gdzie z kolei wybieramy Enterprise Application.
Zatwierdzamy wybór przyciskiem Next >.
W okienku dialogowym New Enterprise Application w polu Project Name wpisujemy Phonebook.
Definicja serwera aplikacyjnego GlassFish v2 w NetBeans IDE
Wybieramy przycisk Add przy polu Server i w oknie dialogowym Add Server Instance wybieramy GlassFish V2.
Wciskamy przycisk Next >.
W kolejnym kroku Platform Folder Location podajemy katalog domowy serwera - pole Platform Location, np. c:\apps\glassfish.
Wciskamy przycisk Next >.
Podajemy dane uwierzytelniające użytkownika z prawami administracyjnymi (domyślnie admin/adminadmin)
i kończymy proces wybierając przycisk Finish.
Na zakończenie powinniśmy otrzymać okienko dialogowe z wypełnionymi wszystkimi obowiązkowymi polami, m.in. Server oraz Java EE Version.
Wciskamy przycisk Finish.
Utworzenie projektu JPA - Phonebook-jpa
Poza domyślnie utworzonymi projektami dodatkowymi Phonebook-ejb oraz Phonebook-war, które składają się na naszą aplikację korporacyjną, stworzymy jeszcze jeden Phonebook-jpa zawierający encję.
Wybieramy menu File > New Project... i w okienku dialogowym wybieramy kategorię Java, w której z kolei wybieramy Java Class Library.
Wciskamy przycisk Next >.
W oknie dialogowym New Java Class Library - Name and Location wpisujemy:
- Project Name: Phonebook-jpa
- Project Location: katalog aplikacji korporacyjnej zdefiniowanej w poprzednim kroku (wystarczy do ścieżki w polu Project Location dodać Phonebook). Uprości to znacząco późniejszą administrację i ewentualną dystrybucję projektu aplikacji.
Wciskamy przycisk Finish.
Utworzenie encji - Subscriber
Do utworzenia encji Subscriber korzystamy z menu kontekstowego New > Entity Class... w projekcie Phonebook-jpa.
W oknie dialogowym New Entity Class - Name and Location definiujemy:
- Class Name: Subscriber
- Package: org.apache.geronimo.samples.phonebook
Definicja jednostki utrwalania - persistence.xml
Wciskamy przycisk Create Persistence Unit..., gdzie wybieramy jdbc:derby://localhost:1527/sample [app on APP] jako Database Connection oraz Drop and Create w Table Generation Strategy.
Wciskamy przycisk Create.
Po tym kroku komunikat dotyczący braku konfiguracji JPA znika, więc wciskamy przycisk Finish.
Użycie encji w środowisku serwera aplikacyjnego Java EE 5 wymaga modyfikacji pliku persistence.xml, gdyż ten utworzony przez NetBeans IDE 6.0 zakłada użycie JPA w trybie poza serwerem.
Otwieramy plik persistence.xml, który znajduje się pod węzłem Phonebook-jpa > Source Packages > META-INF. Wybieramy widok XML (domyślnie plik prezentowany jest w widoku Design).
Modyfikujemy plik persistence.xml tak, aby zawierał następującą treść:
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
version="1.0">
<persistence-unit name="Phonebook-jpaPU">
<jta-data-source>jdbc/sample</jta-data-source>
<properties>
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
Dodanie pól trwałych encji - name i phoneNumber
Do klasy encji dodajemy dwa dodatkowe pola trwałe - name oraz phoneNumber.
private String name; private String phoneNumber;
Z menu kontekstowego klasy (prawy przycisk myszy) wybieramy Refactor > Encapsulate Fields... (jeśli w czasie wybrania menu kursor znajdował się na polu to jego metody ustawiająca i odczytująca zostaną automatycznie zaznaczone w oknie dialogowym tworzenia metod).
i zaznaczamy pola przy getName, setName oraz getPhoneNumber i setPhoneNumber.
Zatwierdzamy wybór przez wciśnięcie przycisku Refactor.
Definicja nazwanego zapytania - Subscriber.findAll
Zgodnie z wytycznymi odnośnie nazewnictwa zapytań nazwanych (ang. named queries) opisanych w dokumencie Using Named Queries poprzedzamy nazwę zapytania nazwą encji, do której należy, tj. Subscriber.findAll.
@NamedQuery(name = "Subscriber.findAll", query = "SELECT s FROM Subscriber s")
Komunikat błędu związany z niedostępnością typu NamedQuery rozwiązujemy przez menu kontekstowe Fix Imports (alternatywnie Ctrl+Shift+I).
Utworzenie konstruktorów encji
Dla uproszczenia pracy z encją tworzymy dodatkowy konstruktor, który przyjmuje parametry name oraz phoneNumber. Wymaganiem specyfikacji JPA jest dostępność domyślnego konstruktora bezparametrowego, więc jeśli tworzymy nowy parametryzowany konstruktor musimy również zdefiniować konstruktor bezparametrowy.
Z menu kontekstowego klasy encji (w ramach edytora klasy Subscriber.java wciskamy prawy przycisk myszy) wybieramy Insert Code... (alternatywnie Alt+Insert).
Z menu wybieramy Constructor....
W kolejnym oknie dialogowym Generate Constructor zaznaczamy pola przy name oraz phoneNumber.
Zatwierdzamy wciskając przycisk Generate.
Powtarzamy czynność, jednakże w oknie dialogowym Generate Constructor nie zaznaczamy żadnych pól, a jedynie wciskamy przycisk Generate (kombinacja klawiszy: Alt+Insert, Enter, Enter). W ten sposób utworzymy konstruktor bezparametrowy.
Encja Subscriber w całej okazałości
Ostatecznie nasza klasa encji Subscriber prezentuje się następująco:
package org.apache.geronimo.samples.phonebook;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
@Entity
@NamedQuery(name = "Subscriber.findAll", query = "SELECT s FROM Subscriber s")
public class Subscriber implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String phoneNumber;
public Subscriber() {
}
public Subscriber(String name, String phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
public void setId(Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Utworzenie sesyjnego ziarna EJB - Phonebook
Zaznaczamy projekt Phonebook-ejb i wybieramy z menu kontekstowego (dostępnego pod prawym klawiszem myszki) New > Session Bean....
W okienku dialogowym New Session Bean - Name and Location wpisujemy:
- EJB Name: Phonebook
- Package: org.apache.geronimo.samples.phonebook
Zatwierdzamy wciskając przycisk Finish.
Deklaracja zależności projektowej - Phonebook-jpa
Nasze ziarno EJB będzie korzystało z encji Subscriber oraz jednostki utrwalania Phonebook-jpaPU, które są dostępne w ramach projektu Phonebook-jpa. Dodajemy zależność projektu Phonebook-ejb od projektu Phonebook-jpa. Zaznaczamy węzeł Libraries w projekcie Phonebook-ejb i z menu kontekstowego wybieramy Add Project....
Wskazujemy na projekt Phonebook-jpa.
Deklaracja zarządcy encji Phonebook-jpaPU
Definiujemy pole em dla kontekstu trwałego Phonebook-jpaPU ziarna. Dodajemy poniższy kod do klasy PhonebookBean.
@PersistenceContext(unitName = "Phonebook-jpaPU") protected EntityManager em;
Pojawi się błąd związany z niedostępnością odpowiednich deklaracji (importów) pakietów. Wybieramy z menu kontekstowego klasy (dostępnego pod prawym klawiszem myszy) Fix Imports.
Utworzenie metody biznesowej - createSubscriber
Zgodnie z komentarzem w klasie PhonebookBean
// Add business logic below. (Right-click in editor and choose // "EJB Methods > Add Business Method" or "Web Service > Add Operation")
korzystamy z menu kontekstowego EJB Methods > Add Business Method.
W polach podajemy następujące wartości:
- Name: createSubscriber
- Return Type: Subscriber
i dodajemy dwa parametry wejściowe za pomocą przycisku Add.
Zatwierdzamy przyciskiem OK.
Wypełniamy ciało metody createSubscriber następującą treścią:
public Subscriber createSubscriber(String name, String phoneNumber) {
Subscriber subscriber = new Subscriber(name, phoneNumber);
em.persist(subscriber);
return subscriber;
}
Utworzenie metody biznesowej - getSubscribers
Podobnie jak miało to miejsce w przypadku tworzenia metody createSubscriber korzystamy z menu kontekstowego EJB Methods > Add Business Method, gdzie w formatce Add Business Method... podajemy:
- Name: getSubscribers
- Return Type: List<Subscriber>
Zatwierdzamy przyciskiem OK.
Wciskamy Ctrl+Shift+I (menu kontekstowe Fix Imports), aby zlikwidować błąd związany z brakiem importu dla typu List<Subscriber>.
Wciskamy przycisk OK.
Modyfikujemy treść metody getSubscribers, aby prezentowała się następująco:
public List<Subscriber> getSubscribers() {
return (List<Subscriber>) em.createNamedQuery("Subscriber.findAll").getResultList();
}
Kończymy wybierając menu Source > Fix Code... stojąc kursorem na właśnie wprowadzonej linii (bądź alternatywnie wciskając Alt+Enter).
i wybieramy pozycję Suppress Warning - unchecked.
Przechodzimy do interfejsu lokalnego PhonebookLocal, gdzie wykonujemy Fix Imports.
PhonebookBean w całej okazałości
Klasa ziarna PhonebookBean powinna prezentować się następująco:
package org.apache.geronimo.samples.phonebook;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class PhonebookBean implements PhonebookLocal {
@PersistenceContext(unitName = "Phonebook-jpaPU")
protected EntityManager em;
public Subscriber createSubscriber(String name, String phoneNumber) {
Subscriber subscriber = new Subscriber(name, phoneNumber);
em.persist(subscriber);
return subscriber;
}
@SuppressWarnings("unchecked")
public List<Subscriber> getSubscribers() {
return (List<Subscriber>) em.createNamedQuery("Subscriber.findAll").getResultList();
}
}
PhonebookLocal w całej okazałości
Lokalny interfejs biznesowy PhonebookLocal powinien prezentować się następująco:
package org.apache.geronimo.samples.phonebook;
import java.util.List;
import javax.ejb.Local;
@Local
public interface PhonebookLocal {
Subscriber createSubscriber(String name, String phoneNumber);
List<Subscriber> getSubscribers();
}
Dodanie biblioteki JSF do projektu Phonebook-war
Z menu kontekstowego projektu Phonebook-war (dostępnym pod prawym klawiszem myszy) wybieramy Properties.
W panelu Project Properties - Phonebook-war wybieramy Frameworks.
Wciskamy przycisk Add...
W oknie dialogowym Add a Framework zaznaczamy JavaServer Faces.
Wciskamy przycisk OK.
Zatwierdzamy przyciskiem OK.
Utworzenie ziarna zarządzanego JSF - PhonebookAgent
Do prezentacji danych będzie nam potrzebne ziarno zarządzane (ang. managed bean) - PhonebookAgent, który wywołania metod będzie po prostu delegował do swojego odpowiednika ziarna EJB PhonebookBean.
Z menu kontekstowego projektu Phonebook-war wybieramy New > Other...
W oknie dialogowym New File wybieramy kategorię Java Server Faces a następnie JSF Managed Bean.
Wciskamy przycisk Next >.
Formatkę wypełniamy następującymi wartościami:
- Class Name: PhonebookAgent
- Package: org.apache.geronimo.samples.phonebook
Kończymy przyciskiem Finish.
Dodanie zależności ziarna EJB PhonebookBean do ziarna zarządzanego PhonebookAgent
Z menu kontekstowego wybieramy Enterprise Resources > Call Enterprise Bean.
Wybieramy jedyne dostępne ziarno PhonebookBean.
i wciskamy przycisk OK.
Właściwości zarządzanego ziarna - name oraz phoneNumber
Modyfikujemy klasę PhonebookAgent o 2 nowe pola name oraz phoneNumber, które będą odpowiadały (logicznie) polom w encji Subscriber.
private String name; private String phoneNumber;
Generujemy dla nich metody modyfikującą i odczytującą poprzez menu kontekstowe Insert Code... (alternatywnie Alt+Insert) i wybierając Getter and Setter....
lub alternatywnie poprzez menu kontekstowe Refactor > Encapsulate Fields.... Wybór należy do Ciebie. Ostatecznie powinny powstać metody para setName() i getName() oraz setPhoneNumber() i getPhoneNumber().
public class PhonebookAgent {
...
private String name;
private String phoneNumber;
...
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Metoda PhonebookAgent.create()
Dodajemy metodę create() do ziarna zarządzanego PhonebookAgent. Metoda będzie wywoływana z formularza i wywołanie wydeleguje do ziarna EJB PhonebookBean, które zostało przekazane przez mechanizm wstrzeliwania zależności udostępniony w serwerze aplikacyjnym Java EE 5 przez adnotację @EJB.
public String create() {
phonebookBean.createSubscriber(getName(), getPhoneNumber());
return null;
}
Metoda zwraca null, co w JSF oznacza ponowne wyświetlenie strony, z której metoda-akcja create() była wywołana.
Metoda PhonebookAgent.getSubscribers()
Podobnie jak z metodą create(), metoda getSubscribers() jest odpowiednikiem metody getSubscribers() w ziarnie EJB PhonebookBean i do niej zostanie wydelegowane wywołanie.
public List<Subscriber> getSubscribers() {
return phonebookBean.getSubscribers();
}
Komunikat błędu związany z niedostępnością typu List rozwiązujemy przez kombinację klawiszy Ctrl+Shift+I bądź menu kontekstowe Fix Imports. Błędy związane z brakiem dostępu do klasy Subscriber rozwiążemy w kolejnym kroku.
Dodanie projektu encji Phonebook-jpa jako zależności projektowej
Zaznaczamy węzeł Libraries w projekcie Phonebook-war i poprzez menu kontekstowe Add Project... dodajemy zależność projektową Phonebook-jpa (dokładniej katalog, w którym projekt Phonebook-jpa występuje).
Dodanie zależności rozwiązuje problem niedostępności klasy Subscriber.
Klasa PhonebookAgent w pełnej okazałości
Klasa PhonebookAgent powinna prezentować się następująco:
package org.apache.geronimo.samples.phonebook;
import java.util.List;
import javax.ejb.EJB;
public class PhonebookAgent {
@EJB
private PhonebookLocal phonebookBean;
private String name;
private String phoneNumber;
public PhonebookAgent() {
}
public String create() {
phonebookBean.createSubscriber(getName(), getPhoneNumber());
return null;
}
public List<Subscriber> getSubscribers() {
return phonebookBean.getSubscribers();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Utworzenie strony domowej aplikacji JSF - phonebook.jsp
Rozpoczniemy od porządków w już istniejących stronach JSP. Najpierw usuniemy stronę index.jsp, która dostępna jest pod węzłem Phonebook-war > Web Pages.
UWAGA: Jeśli pojawi się komunikat o niemożności usunięcia pliku, spróbuj jeszcze raz. Można również skorzystać z alternatywnego sposobu - za pomocą klawisza Delete.
Następnym krokiem w naszych porządkach jest zmiana nazwy strony welcomeJSF.jsp na phonebook.jsp za pomocą menu kontekstowego Rename... (alternatywnie klawisz F2).
Modyfikujemy zawartość strony phonebook.jsp tak, aby treść strony była następująca:
<%@page pageEncoding="UTF-8" contentType="text/html"%>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Phone book</title>
</head>
<body>
<f:view>
<h:form>
Name: <h:inputText value="#{PhonebookAgent.name}" />
Phone number: <h:inputText value="#{PhonebookAgent.phoneNumber}" />
<h:commandButton value="Create" action="#{PhonebookAgent.create}" />
</h:form>
<HR>
<h:dataTable value="#{PhonebookAgent.subscribers}" var="subscriber" border="1">
<h:column>
<f:facet name="header">
<h:outputText value="Name" />
</f:facet>
<h:outputText value="#{subscriber.name}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Phone number" />
</f:facet>
<h:outputText value="#{subscriber.phoneNumber}" />
</h:column>
</h:dataTable>
</f:view>
</body>
</html>
Uruchomienie
Uruchomienie aplikacji zaczniemy od modyfikacji ustawień projektu głównego Phonebook. Zaznaczamy węzeł projektu Phonebook i otwieramy właściwości projektu przez menu kontekstowe Properties.
Wybieramy Run i wypełniamy Relative URL wartością /faces/phonebook.jsp.
Zatwierdzamy zmiany przyciskiem OK.
Wybieramy menu kontekstowe Build, a po poprawnym zbudowaniu projektu wybieramy menu Run.
Podczas wykonania polecenia Run następuje uruchomienie serwera GlassFish oraz bazy danych Java DB. Poprawne uruchomienie kończy się poniższym komunikatem w oknie Phonebook (run).
... Building jar: C:\Documents and Settings\jlaskowski\My Documents\NetBeansProjects\Phonebook\dist\Phonebook.ear post-dist: dist: pre-run-deploy: Initial deploying Phonebook to C:\Documents and Settings\jlaskowski\My Documents\NetBeansProjects\Phonebook\dist\gfdeploy Completed initial distribution of Phonebook Start registering the project's server resources Finished registering server resources moduleID=Phonebook deployment started : 0% deployment finished : 100% Deploying application in domain completed successfully Trying to create reference for application in target server completed successfully Trying to start application in target server completed successfully Deployment of application Phonebook completed successfully Enable of Phonebook in target server completed successfully Enable of application in all targets completed successfully All operations completed successfully post-run-deploy: run-deploy: Browsing: http://localhost:8080/Phonebook-war/faces/phonebook.jsp run-display-browser: run-ac: run: BUILD SUCCESSFUL (total time: 1 minute 33 seconds)
oraz uruchomieniem okna przeglądarki z adresem http://localhost:8080/Phonebook-war/faces/phonebook.jsp.
Teraz pozostaje uaktrakcyjnienie interfejsu użytkownika o kontrolki graficzne JSF i dodanie gadżetów ajaksowych. Decyzję pozostawiam Tobie! Z zainteresowaniem przeczytam dokonania w tej materii, więc jeśli masz pomysły na potencjalne rozszerzenia, skontaktuj się ze mną. Miłego programowania z Korporacyjną Javą!
