inhoudstafel en auteursrecht
* STER *


11. Enterprise JavaBeans

11.1 Waarom, wat, wanneer

Bedrijfstoepassingen moeten schaalbaar zijn. Dat wil zeggen dat ze met het bedrijf mee moeten groeien, liefst zonder compleet te worden herschreven. Neem bijvoorbeeld de boekhouding van een kruidenierszaak. Aanvankelijk gebeurt die op één enkel PC-tje in de winkel, dat tevens als kassa dienstdoet. Op een bepaald moment transformeert de zaak zich in een minimarkt. De twee kassa's worden natuurlijk gekoppeld aan de boekhouding, die inmiddels naar het kantoortje achterin is verhuisd: lokaal netwerk. De minimarkt is succesvol, koopt een paar concurrenten, en voor je het weet is een supermarktketen geboren: wide area networking met redundante centrale servers, enzovoort.

In het voorbeeld hierboven zien we dat Java van nature enkele sterke troeven in handen houdt. Met name de platform-onafhankelijkheid maakt het mogelijk, één ontwikkelings- en uitbatingsplatform te hanteren vanaf de kleinste netwerkmachientjes (zoals kassa's) tot de krachtigste centrale mainframe.

Een belangrijk aspect van de schaalbaarheid is gedistribueerd programmeren, dat wil zeggen dat verschillende componenten van het informatiesysteem op verschillende machines draaien. Die machines (dus de componenten van het systeem) moeten met elkaar praten.

Communicatie tussen verschillende machines is in Java op meer dan één manier mogelijk. De meest natuurlijke aanpak is Remote Method Invocation of kortweg RMI. Door RMI kan een Java-programma op één machine een methode aanroepen van een klasse die in het geheugen van een andere machine zit. Programmeertechnisch is het niet zo moeilijk: het volstaat dat de klasse die aangeroepen wordt, een bepaalde interface implementeert. Om de twee programma's te doen draaien, is nog een beetje configuratiewerk op de twee machines nodig. Er moet ook wat extra code geschreven worden om een object van de "server"-klasse te creëren en aan te melden (zodat andere programma's haar methoden kunnen aanroepen). Vooral dit laatste kan, bij een complex informatiesysteem met een groot aantal RMI-klassen, aanleiding geven tot moeilijk onderhoudbare code.

Om programmeurs af te schermen van de techniciteit van RMI, én om toe te laten dat dezelfde methode zowel lokaal als gedistribueerd wordt opgeroepen (schaalbaarheid!) ontwikkelde Sun het begrip Enterprise JavaBean of EJB. Een EJB is een JavaBean (zie hoofdstuk 8) die speciaal gemaakt is om via RMI te worden aangeroepen. Een EJB kan ook zelf via RMI andere EJBs aanroepen. EJBs worden automatisch gecreëerd zonder dat de programmeur zelf nog opstart- of aanmeldcode moet schrijven.

Sun creëerde EJBs samen met een aantal andere technieken (JSP, JNDI, Connector Architecture) en biedt het geheel aan onder de naam Java 2 Enterprise Environment (J2EE). EJBs de kern van de bundel. Door hun nauwe integratie met J2EE komen EJBs vaak voor in toepassingen die de andere aspecten van J2EE gebruiken, bijvoorbeeld in webapplicaties. In het algemeen zijn EJBs populair als een systeem uit verschillende software-lagen bestaat, bijvoorbeeld database - business logic - grafische presentatie.

EJBs hebben slechts één nadeel ten opzichte van "gewone" Java-klassen: ze gebruiken nogal wat geheugen en processor-capaciteit. De traditionele voorsprong van Java-programma's ten opzichte van visuele "kant-en-klaar" omgevingen gaat hierdoor gedeeltelijk verloren.

De volgende tabel vat samen wanneer het gebruik van EJBs aan te bevelen is bij de ontwikkeling van een nieuw informatiesysteem.

Systeemaspect Gebruik géén EJBs als... Gebruik wél EJBs als...
Hardware/Architectuur het systeem per definitie nooit over verscheidene machines kan gespreid worden (bv. systeemprogramma) er een redelijke hoop bestaat dat het systeem binnen vijf jaar te groot is voor één machine (bv. beheer aandelenportefeuilles)
Hardware geheugen en processortijd uiterst beperkt of kostbaar zijn (bv. actiespelletje) de machines goedkoper zijn dan de programmeurs (bv. kassa)
Businessmodel het systeem moet verkocht - of geschonken - worden aan een groot aantal particulieren (bv. applet) het systeem voor professioneel gebruik bestemd is (bv. boekhouding)
Software-architectuur het systeem een alleenstaande utility is die geen gebruik maakt van een database en die niet is uitgerust met een grafische gebruikersinterface (bv. batchprogramma) het systeem van nature uit verschillende lagen bestaat (bv. data mining)
Interactie het systeem op zichzelf staat (bv. rekenmachine) het systeem moet interageren met bestaande software, al dan niet op dezelfde machines (bv. reservering van hotelkamers)

11.2 Ingrediënten van een EJB

Om een EJB te ontwikkelen, moet de programmeur niet minder dan drie bronbestanden leveren:

Een EJB is in de eerste plaats een dienstverlenend object. Het ontleent zijn nut aan het feit dat andere Java-code zijn methoden kan aanroepen. De client interface (ook bean interface of remote interface genoemd) is een gewone Java-interface (zie paragraaf 4.8) waarin de signatuur van de extern bruikbare methoden wordt gespecificeerd. De client-interface moet een extensie zijn van de gegeven interface javax.ejb.EJBObject. Deze laatste is op zijn beurt een extensie van de interface java.rmi.Remote, een EJB is dus een remote object in de betekenis van Remote Method Invocation. Dat betekent ondermeer dat alle methoden van de client interface een RemoteException kunnen veroorzaken.

De home interface is ook een Java-interface, maar de methoden die erin voorkomen zijn geen methoden van de EJB zelf. Het zijn factory-methoden die de client in staat stellen een object van het gewenste EJB-type terug te vinden of te creëren. De home interface is een uitbreiding van de standaard-interface javax.ejb.EJBHome.

De implementatie van de bean is niets anders dan de klassendefinitie. Deze klasse zal de implementatie leveren van de nodige functionaliteit om de twee hogergenoemde interfaces in te vullen. De enterprise javabean implementeert de interface javax.ejb.EnterpriseBean, meestal via een van de drie subinterfaces die de drie standaardtypes EJB vertolken:

javax.ejb.SessionBean javax.ejb.EntityBean javax.ejb.MessageDrivenBean

Opmerkingen

  1. Om technische redenen kan een bean twee verschillende client interfaces hebben: de local interface en de remote interface. In dat geval kunnen de methoden van de remote interface worden aangeroepen vanaf eender welke virtuele Java-machine, terwijl de methoden van de local interface (typisch groter in aantal) slechts kunnen worden aangeroepen vanuit de virtuele machine waarin zich het EJB-object bevindt. Op dezelfde manier kan een onderscheid gemaakt worden tussen de ("gewone") home interface en de local home interface. Door gebruik te maken van de lokale interfaces, vermijdt men RMI-aanroepen en wint men snelheid. Het belangrijkste nadeel is verlies van schaalbaarheid: het computernetwerk is niet langer onzichtbaar voor de programmeur. In dit boek gaan we niet verder in op dit technische onderscheid, en we beperken ons tot de meer universele remote interfaces.
  2. Een EJB implementeert meestal niet haar interfaces in de zin van het sleutelwoord implements (zie paragraaf 4.8). De functionaliteit van de methoden van beide interfaces wordt geleverd door bean-methoden met lichtjes verschillende namen. Het uitbatingssysteem ('bean container') legt het verband.

Voorbeeld

We construeren een demonstratiebean die als "dienstverlening" niet meer doet dan een eenvoudige groet opsturen in de vorm van een constante tekststreng. De client interface ziet er als volgt uit.

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface Groet extends EJBObject {
  public String zegHet() throws RemoteException;
}

De home interface bevat niet meer dan een methode om een object van het type Groet aan te maken.

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface GroetHome extends EJBHome {
  Groet create() throws RemoteException, CreateException;
}

De uitzondering javax.ejb.CreateException signaleert een algemeen probleem bij het aanmaken van een object van het type Groet.

De implementatie van onze bean gaat via de interface SessionBean.

import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class GroetBean implements SessionBean {
  public String zegHet() {
    return "Hallo, EJB!";
  }

  public void ejbCreate() {}
  public void ejbRemove() {}
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void setSessionContext(SessionContext sc) {}
}

De methode zeghet komt overeen met de gelijknamige methode in de remote interface. De methode ejbCreate is nodig omdat we een create-methode hebben in de home interface. De andere vier methoden worden opgelegd door de interface SessionBean.

De drie bovenstaande bronbestanden volstaan niet om een enterprise javabean te definiëren. We hebben ook een XML-bestand nodig dat vastlegt hoe deze drie bestanden samenwerken. Dit XML-bestand is de deployment descriptor. In de volgende paragraaf geven we aan hoe de deployment descriptor van Groet er zou kunnen uitzien.

Om de drie bronbestanden te compileren, moet je de compiler vertellen waar hij de definitie van klassen zoals javax.ejb.SessionBean enz. kan vinden; die zitten namelijk niet in de standaard JDK. In de volgende paragraaf installeren we de applicatieserver JBoss; als onderdeel van die installatie zullen we over de nodige jarfiles beschikken waarin de J2EE-klassen zitten opgeslagen. We stellen de compilatie uit tot dan.

In voorbereiding:

11.3 Bean Container

De Bean Container of EJB Container is een computerprogramma dat de EJB-productie-omgeving creëert. Dat wil zeggen dat dit programma permanent moet draaien op minstens één server die voor de gebruikers toegankelijk is. De EJB-container leest de configuratiebestanden voor de verschillende enterprise javabeans, en creëert deze beans naargelang de behoefte. Hij is tevens verantwoordelijk voor het bewaren en terug oproepen van beans met persistente attributen, voor het consistente beheer van transacties, en voor het gecentraliseerde beheer van gedeelde hulpbronnen (resource pooling). Op de softwaremarkt worden bean containers vaak verpakt in een geheel samen met een webserver, een naamserver, een JSP-server enzovoort; dat geheel heet dan een application server. Op het moment dat we dit schrijven, is de markt voor application servers nog niet helemaal tot rust gekomen. Zeker is dat de Open Source-beweging hier een sterke positie heeft. In deze tekst kiezen we voor het Open Source-product JBoss. Volgens voorstanders is deze application server de meest robuuste (minst foutvrije) van het zootje, en is hij bovendien relatief eenvoudig te beheren.

Je kan JBoss downloaden vanaf de site http://www.jboss.org. Ga op zoek naar "downloads", vervolgens naar "JBoss Application Server". Op het moment dat we dit schrijven, is versie 4.0.0 de meest recente stabiele release. Hij wordt aangeboden op de download-site SourceForge (http://sourceforge.net) in diverse compressieformaten. Je kan in principe eender welk formaat downloaden waarvoor je het nodige decomprimeerprogramma bezit, en je hebt ook de keuze tussen een source release (met broncode, herkenbaar aan de letters src in de bestandsnaam) of een binary release (alleen de installatieset). Wij opteren in dit voorbeeld voor het ZIP-archief met de naam jboss-4.0.0.zip

Bewaar het archief op een bekende plaats. Extraheer zijn inhoud naar een map waarvan de volledige padnaam geen spaties bevat Goede voorbeelden zijn: de root-map c:\ of de map c:\dev; een slecht voorbeeld is: c:\program files\jboss (want dat bevat een spatie). Wij creëren eerst een map c:\dev en extraheren de inhoud van het zip-archief daarheen.

Op het bovenste niveau van het ZIP-archief staat een map met de naam jboss-4.0.0 (of in jouw geval, iets gelijkaardigs met een ander versienummer).

JBoss heeft een Java-omgeving nodig om te werken. Minimaal is dat een Java Runtime Environment (een virtuele machine), maar om JSP's te draaien heb je ook een compiler nodig. JBoss 4.0.0 heeft minimaal Java versie 1.4 nodig. Wij hebben een complete JDK 1.5 geïnstalleerd in de map c:\dev\jdk1.5.0 We moeten aan JBoss laten weten waar hij de JRE kan vinden door de systeemveranderlijke JAVA_HOME een waarde te geven die gelijk is aan de padnaam van de top van de Java-installatie. Het hangt af van je beheerssysteem hoe je systeemveranderlijken kan creëren; in Microsoft Windows XP gaat dit via de utility Systeem op het Configuratiepaneel (Control Panel/System). Druk in het tabblad "Geavanceerd" (Advanced) op de knop "Omgevingsveranderlijken" (Environment Variables) en creëer in de onderste helft van het dialoogvenster een nieuwe veranderlijke met de naam JAVA_HOME en als waarde de padnaam van je JDK-installatie.

Environment Variables - system variables - JAVA_HOME=c:\dev\jdk1.5.0

Test je installatie als volgt. Open een opdrachtvenster ("Command Prompt" in MS Windows). Test of de omgevingsveranderlijke de juiste waarde heeft door te typen

echo %JAVA_HOME%

Het systeem moet nu antwoorden met de padnaam van je JDK-installatie. Ga vervolgens naar de deelmap bin van de installatiemap van JBoss, en start de server met het commando run.

cd \dev\jboss-4.0.0\bin
run

Als alles goed gaat, moet JBoss nu starten. Je ziet in het commandovenster een lange reeks technische boodschappen verschijnen, met op het einde een melding van de duurtijd van de opstartprocedure.

22:33:16,859 INFO  [Server] JBoss (MX MicroKernel) [4.0.0 (build: CVSTag=JBoss_4_0_0 date=200409200418)] Started in 16s:703ms

Controleer dat de webserver van JBoss effectief bereikbaar is door in een browser het adres http://localhost:8080 aan te roepen.

Welcome To JBoss

Je kan JBoss terug uitschakelen door in hetzelfde commandovenster de loop van het programma te onderbreken (in MS-DOS: met de toetscombinatie Ctrl-C).

We zullen nu de EJB Groet van de vorige paragraaf ontplooien in JBoss. In elk geval hebben we een deployment descriptor nodig. Dat is een XML-bestand dat ondermeer aangeeft welke Java-bestanden samen een EJB uitmaken. De deployment descriptor van Groet draagt de naam ejb-jar.xml en ziet er als volgt uit.

<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar>
 <description>Eenvoudigste EJB-applicatie</description>
 <display-name>Hallo, EJB demonstratietoepassing</display-name>
 <enterprise-beans>
  <session>
   <display-name>Hallo, EJB demonstratiebean</display-name>
   <ejb-name>GroetBean</ejb-name>
   <home>GroetHome</home>
   <remote>Groet</remote>
   <ejb-class>GroetBean</ejb-class>
   <session-type>Stateless</session-type>
   <transaction-type>Container</transaction-type>
  </session>
 </enterprise-beans>
</ejb-jar>

We moesten onze drie Java-bronbestanden ook nog compileren. Als onderdeel van de JBoss-installatie vind je een map server...all...lib met daarin ondermeer het archief jboss-j2ee.jar. Neem die jarfile op in het classpath via de opdrachtregel van de compiler. Bij ons is JBoss geïnstalleerd in de map c:\dev\jboss-4.0.0, dus we compileren met

javac -cp c:\dev\jboss-4.0.0\server\all\lib\jboss-j2ee.jar;. *.java

De definitie van onze bean bestaat nu al uit vier verschillende bestanden. Het geheel kan best aan JBoss worden aangeboden in de vorm van een Java-archief (jarfile, zie hoofdstuk 5b). De interne structuur van dat archief, dat we hier voorbeeld.jar noemen, is de volgende. De deployment descriptor gaat in de deelmap META-INF, die reeds het manifest bevat. De gecompileerde klassenbestanden GroetBean.class, Groet.class en GroetHome.class komen op het hoogste niveau.

voorbeeld.jar:
     |
     ----------- META-INF
     |              |
     |              -------------- ejb-jar.xml
     |
     ----------- Groet.class
     |
     ----------- GroetBean.class
     |
     ----------- GroetHome.class

Hier is een stappenplan voor de creatie van het archief:

  1. Creëer een directory (map) met de naam META-INF op de plaats waar zich de klassenbestanden bevinden;
  2. verplaats de deployment descriptor naar deze nieuwe map;
  3. open een DOS-venster, zorg ervoor dat de PATH-veranderlijke de JDK kent (compiler enz.), en typ het commando
  jar cvf voorbeeld.jar Groet.class GroetBean.class GroetHome.class META-INF

Inspecteer de inhoud van de jarfile met een ZIP-tool, bijvoorbeeld 7Zip. Verplaats vervolgens de jarfile naar de map C:\dev\jboss-4.0.0\server\default\deploy (of een gelijkaardige plaats, afhankelijk van waar je JBoss hebt geïnstalleerd). De bean wordt nu automatisch ontplooid, zonder dat je de server moet herstarten! In het opdrachtvenster verschijnt een boodschap in de volgende trant:

22:44:28,156 INFO  [EjbModule] Deploying GroetBean
22:44:28,218 INFO  [EJBDeployer] Deployed: file:/C:/dev/jboss-4.0.0/server/default/deploy/voorbeeld.jar

Je kunt de aanwezigheid van de nieuwe bean ook via de management console van JBoss waarnemen: ga naar de webpagina http://localhost:8080/jmx-console/ en zoek naar het optreden van de tekst "Groet" (via de zoekfunctie in de pagina, bij MS Internet Explorer is dit Ctrl-F).

jndiName=GroetBean,plugin=pool,service=EJB

De beste controle of onze bean correct ontplooid is en goed werkt, is hem gebruiken in een clientprogramma. Voor de gelegenheid gebruiken we een heel eenvoudige client, gelanceerd vanaf de opdrachtregel. In praktische situaties zullen EJBs vaak worden aangesproken vanuit Java-objecten die eveneens op een applicatieserver draaien, bijvoorbeeld JSP-pagina's. Hier is alvast de code van onze voorbeeldclient.

import javax.naming.*;
import javax.rmi.PortableRemoteObject;

/** Eenvoudige client om aan te tonen dat de EJB GroetBean
 *  ontplooid is en werkt. De opdrachtregel moet de volgende
 *  parameters specificeren:
 *    java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
 *    java.naming.provider.url=localhost:1099
 */
public class GroetClient {

  /** Creëer een instantie van GroetBean en vang haar boodschap op. */
  public static void main(String[] args) {

    try {
      InitialContext jndiContext = new InitialContext();
      Object ref  = jndiContext.lookup("GroetBean");
      GroetHome home = (GroetHome)
        PortableRemoteObject.narrow (ref, GroetHome.class);
      Groet deBean = home.create();
      System.out.println("Boodschap van Groet-bean luidt:");
      System.out.println(deBean.zegHet());
    }
    catch(Exception e) {
      System.out.println(e.toString());
    }
  }
}

Vergelijk dit programma met het voorbeeld van paragraaf 6b.3. Een EJB container heeft altijd een naming service, en elke Enterprise JavaBean word automatisch bij die service geregistreerd als onderdeel van zijn normale deployment. We krijgen dus van de container een referentie naar de gezochte bean, die we opslaan in de veranderlijke ref.

De naming context factory van de JBoss naming service zit in de klasse org.jnp.interfaces.NamingContextFactory (jnp staat voor JBoss Nameservice Provider). De naming service wordt aangesproken via een speciaal toepassingsprotocol bovenop IP, dat standaard gebruik maakt van poort nummer 1099. De naam waaronder een EJB geregistreerd staat, komt overeen met het element <ejb-name> in zijn deployment descriptor.

Om de broncode van GroetClient te compileren, volstaat het dat de compiler toegang heeft tot de gewone J2EE-klassen. Compileer bijvoorbeeld zoals hierboven met de opdrachtregel

javac -cp c:\dev\jboss-4.0.0\server\all\lib\jboss-j2ee.jar;. GroetClient.java

Om de client te starten moeten we de twee hogergenoemde parameters invullen met de juiste informatie over de naming service. De opdrachtregel zal er ongeveer als volgt uitzien (we splitsen over verschillende regels voor de leesbaarheid, typ dit als één enkele regel):

java
  -cp c:\dev\jboss-4.0.0\client\jbossall-client.jar;.
  -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
  -Djava.naming.provider.url=localhost:1099
  GroetClient

Als je tevoren niet vergeten bent JBoss te starten en de bean te deployen, dan zou het volgende resultaat moeten verschijnen:

Boodschap van Groet-bean luidt:
Hallo, EJB!

Als de client om één of andere reden geen contact krijgt met JBoss, dan zie je eerder iets in de volgende trant.

javax.naming.CommunicationException: Receive timed out
[Root exception is java.net.SocketTimeoutException: Receive timed out]

11.4 Persistentie

In voorbereiding: container managed persistence (CMP) en bean managed persistence (BMP)


inhoudstafel en auteursrecht
* STER *

Valid HTML 4.0! Valid CSS!