Java EE (Java Platform, Enterprise Edition) ist eine durch Schnittstellen definierte Architektur für Unternehmensanwendungen, bestehend aus verschiedenen Komponenten.
EJBs (Enterprise JavaBeans) stellen einen wichtigen Bestandteil von Java EE dar. EJBs sind Komponenten, die serverseitig in einem speziellen EJB-Container ablaufen und die vorrangig zur Implementierung von Geschäftslogik und Persistenz verwendet werden.
Die Vorteile von EJBs sind:
+ Standardisierung, Wartungsfreundlichkeit
+ Verteilte Anwendungen möglich
+ Verteilte Transaktionen
+ Definierte Sicherheitsmechanismen
+ Hohe Ausfallsicherheit erreichbar
+ Hohe Skalierbarkeit
Bekannte Open-Source-EJB-Container sind zum Beispiel: JBoss, GlassFish, Apache Geronimo und JOnAS.
Bekannte kommerzielle EJB-Container sind zum Beispiel: Oracle WebLogic Application Server und IBM WebSphere Application Server.
Dieser Text geht auf EJBs in Version 2 ein. In Version 3 gibt es einige Vereinfachungen.
JavaBeans | Enterprise JavaBeans | |
---|---|---|
Definiert in | Java SE (Java Standard Edition) | Java EE (Java Enterprise Edition) |
Laufzeitumgebung | Kommen ohne Container-Umgebung aus und können in nahezu allen Java-Umgebungen verwendet werden (auch in Standalone-Programmen) | Benötigen speziellen EJB-Container (in Java EE Application Servern) |
Interfaces, Methoden | Einfache Java-Klassen mit 'private' Attributen sowie Getter- und Setter-Methoden (POJO, Plain Old Java Objects) | Genaue Interface-Spezifikationen ('Home', 'Remote' und '...Bean' Interface bei Session und Entity Bean sowie 'MessageDrivenBean' Interface bei Message Driven Bean) |
GUI | Können sowohl sichtbare GUI-Komponenten als auch interne Backend-Komponenten sein | Können niemals sichtbare GUI-Komponente sein |
Restriktionen | Nahezu keine Restriktionen | kein AWT, kein Zugriff auf Dateien per 'java.io', keine eigenen Threads, keine Native-Aufrufe, nur serialisierbare Parameterobjekte |
Kommunikation | Beliebig | Vorwiegend per RMI, RMI-IIOP, JMS, JCA |
Persistenz | Beliebig, z.B. direkte SQL-Kommandos per JDBC oder über Java-Objekte per O/R Mapping (z.B. Hibernate oder JDO) |
Normalerweise in Entity Beans, entweder 'Bean Managed Persistence' (BMP) (sowohl SQL/JDBC als auch O/R Mapping) oder 'Container Managed Persistence' (CMP) |
Standardisierung, Wartungsfreundlichkeit, verteilte Anwendungen, verteilte Transaktionen, Sicherheitsmechanismen, Skalierbarkeit, Clusterfähigkeit |
Muss programmiert werden | Wird vom EJB-Container angeboten |
Einsatz | Beliebig, z.B. in Webapplikationen zusammen mit JSP (z.B. auf Tomcat) | In großen Unternehmensanwendungen (auch Webapplikationen) |
Anfänglicher Lernaufwand | Niedrig | Hoch |
Weitere Informationen zu Restriktionen für EJBs finden Sie unter: http://java.sun.com/blueprints/qanda/ejb_tier/restrictions.html.
Client | |HTTP ............................. | ............................. . | Web Container. . Front Controller . . | . . Business Delegate . ............................. | ............................. |RMI-IIOP ............................. | ............................. . | EJB Container. . Session Facade (Session EJB) . . ____________________|____________________ . . | | | | . . Entity EJB Entity EJB Entity EJB MDB . . | | | | . ........ | ........... | ........... | ........... | ........ | | | | SQL-Datenbank ERP, Legacy JMS
Enterprise JavaBeans bestehen aus mehreren Java-Klassen und Konfigurationsdateien (z.B. XML-Deployment-Descriptoren). Bei Session und Entity Beans sind es mindestens folgende drei Java-Klassen:
Es darf nicht vergessen werden, dass der Client nie direkt mit der EJB-Implementierung interagiert. Die Kommunikation erfolgt per RMI (bzw. RMI-IIOP), die Skeletons und Stubs werden automatisch erzeugt. Das von der 'create()'-Methode im 'Home Interface' returnierte Objekt ist ein clientseitiges Objekt, welches das 'Remote Interface' implementiert, aber es ist nicht das serverseitige Objekt der Bean-Implementierung (wenn es auch damit kommuniziert).
Wenn eine EJB eine Referenz auf eine andere EJB benötigt, sollte nicht direkt nach dem Bean-Namen oder
dem Namen des Home oder Remote Interfaces gesucht werden,
sondern nach dem Namen '"java:comp/env/ejb/MeinEjbJndiName"'.
Die gesamte Sequenz zur Erlangung der Bean-Referenz lautet:
Context ctx = new InitialContext();
Object ref = ctx.lookup("java:comp/env/ejb/MeinEjbJndiName");
IMeinEjbNameHome home = (IMeinEjbNameHome)PortableRemoteObject.narrow(ref,IMeinEjbNameHome.class);
IMeinEjbName meinEjbName = home.create();
Das Initialisieren des 'InitialContext', der 'lookup()' im 'InitialContext' und der 'PortableRemoteObject.narrow()'-Aufruf können mehrere Sekunden dauern. Damit diese Vorgänge nur einmal stattfinden, sollten die 'EJBHome'-Objekte in einem so genannten Service Locator gecacht werden. Siehe hierzu: jee-jndi.htm#ServiceLocator.
Wenn eine Bean eine Referenz auf sich selbst einer anderen Bean übergeben will, darf sie dazu nicht das Schlüsselwort 'this' verwenden. Richtig ist stattdessen 'sessionContext.getEJBObject()' (bzw. 'entityContext.getEJBObject()') (in der Regel mit vorangestelltem Typecast auf den Typ des Objekts). So werden Probleme mit dem in der Bean-Implementierung nicht implementiertem Remote Interface vermieden. Auch benötigen Sie wieder die Methode 'PortableRemoteObject.narrow()'.
Ebenfalls nicht vergessen werden darf, dass der EJB-Container die Lebensdauer der EJBs bestimmt und nicht unbedingt Client-Aufrufe die Zeitpunkte des Erzeugens und Entfernens definieren. EJBs werden eventuell in Pools vorgehalten und bei Bedarf vom Container aktiviert. Selbst den Zeitpunkt des Schreibens in die Datenbank bestimmt bei CMP der Container.
'setSessionContext()' wird am Anfang genau einmal aufgerufen. Aber machen Sie keine Annahmen über die Zeitpunkte und Reihenfolgen der Container-Methodenaufrufe 'ejbActivate()' und 'ejbPassivate()'. Ob und wann diese Methoden aufgerufen werden, bestimmt der Container. Zudem ist das Verhalten bei Session Beans (üblicherweise Pooling) anders als bei Entity Beans (üblicherweise Passivating).
Die Namen der eigenen Methoden dürfen nicht mit 'ejb' beginnen. Eine 'finalize()'-Methode ist nicht erlaubt.
Verlassen Sie sich nicht darauf, dass Parameter stets remote und deshalb 'per Value' übertragen werden. Laufen kommunizierende Beans in derselben JVM, können lokale Zugriffe mit Parameterzugriffen 'per Referenz' konfiguriert werden, so dass die aufgerufene Bean das Originalobjekt des Aufrufers ändern kann.
Denken Sie daran, dass Ihre EJB-Anwendung eventuell in einem Cluster auf mehrere Application Server verteilt wird und parallel mehrfach läuft. Falls Sie 'globale' Variablen benötigen, realisieren Sie dies über die Datenbank oder über das JNDI. Vermeiden Sie möglichst Singletons und statische nicht-final Variablen, bzw. gehen Sie zumindest nicht davon aus, dass statische Variablen und Singletons auf geclusterten Servern in allen VMs den gleichen Wert haben. Falls Sie doch statische Variablen verwenden oder Objekte mehreren Threads zur Verfügung stellen, müssen Sie eventuell Codeabschnitte per 'synchronized' schützen, was allerdings Performance kostet. Weiteres hierzu finden Sie unter http://www.roseindia.net/javatutorials/J2EE_singleton_pattern.shtml, http://java.sun.com/developer/technicalArticles/Programming/singletons und java-concurrency.htm.
In den folgenden Beispielen werden nur Grundlagen erörtert. Dabei werden zum besseren Verständnis alle benötigten Dateien manuell erstellt. In der praktischen Entwicklung wird häufig ein Teil der Arbeit Codegeneratoren überlassen. Bis Java 1.4 und EJB2 empfiehlt sich der Einsatz von XDoclets, ab Java 5 und EJB3 sind die Java-Annotations vorzuziehen.
... siehe: jee-oracleweblogic.htm#Installation.
Die folgenden Beispiele sind alle mit
JBoss
als Java EE Application Server lauffähig.
Zu den ersten Beispielen ist auch die Inbetriebnahme unter Oracle WebLogic beschrieben.
Für andere Java EE Application Server müssen die Beispiele angepasst werden.
Oracle WebLogic
Falls Sie Oracle WebLogic einsetzen: Installieren Sie die Data Source wie beschrieben unter: jee-oracleweblogic.htm#DataSource-MySQL.
JBoss
Falls Sie JBoss einsetzen: Installieren Sie die Data Source wie im Folgenden beschrieben wird.
MySQL
Falls Sie noch keine MySQL-Datenbank installiert haben: Installieren Sie MySQL zum Beispiel wie beschrieben unter: mysql.htm#InstallationUnterWindows. Die folgende Beschreibung geht davon aus, dass Sie als 'Database-Name' "MeineDb" wählen ("CREATE DATABASE MeineDb;") (Sie können auch stattdessen die 'connection-url' im Connector Service anpassen).
MySQL verwendet defaultmäßig die 'MyISAM'-Engine, die aber keine Transaktionen unterstützt. Wenn Sie Transaktionsunterstützung wollen, können Sie die 'InnoDB'-Engine verwenden: Öffnen Sie die zu MySQL gehörende 'my.ini'- oder 'my.cnf'-Datei, zum Beispiel in den Verzeichnissen '/etc/mysql', 'C:\', 'C:\Windows' oder 'C:\Programme\MySQL\MySQL Server 4.1'. Stellen Sie sicher dass nicht 'skip-innodb' gesetzt ist und kontrollieren Sie die anderen 'InnoDB Specific options' (z.B. 'innodb_data_file_path=ibdata:30M').
Downloaden Sie den zur Datenbank passenden JDBC-Treiber, für MySQL zum Beispiel 'mysql-connector-java-5.1.16.zip'. Entpacken Sie die .zip-Datei und kopieren Sie die darin enthaltene Treiberdatei 'mysql-connector-java-5.1.16-bin.jar' in das zu Ihrem JBoss-Server gehörende lib-Verzeichnis, z.B. nach: '<JBOSS_HOME>\server\default\lib'.
JBoss DataSource Connector Service
Registrieren Sie in JBoss (bei gestopptem JBoss) einen geeigneten DataSource Connector Service, indem Sie aus dem '<JBOSS_HOME>\docs\examples\jca'-Verzeichnis die zur Datenbank passende '...-ds.xml'-Datei, also für die MySQL-Datenbank die 'mysql-ds.xml'-Datei, in das zu Ihrem JBoss-Server gehörende deploy-Verzeichnis, also zum Beispiel in das '<JBOSS_HOME>\server\default\deploy'-Verzeichnis, kopieren und zum Beispiel folgendermaßen anpassen:
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>MeinDatasourceJndiName</jndi-name> <connection-url>jdbc:mysql://localhost:3306/MeineDb</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>mysqlpwd</password> <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources>
Passen Sie die Einträge an Ihre Datenbankverbindung an.
Projektverzeichnis
Legen Sie ein Projektverzeichnis an, z.B. 'D:\MeinWorkspace\MeinEjb2Projekt' (im Folgenden '<MeinEjb2Projekt>' genannt) und darin das Unterverzeichnis 'lib'.
AppServer-Client-Lib
Falls es zu Ihrem Java EE Application Server Client-.jar-Libs gibt, kopieren Sie diese in das Unterverzeichnis '<MeinEjb2Projekt>\lib', beispielsweise:
für App-Server | Client-.jar-Lib | aus Verzeichnis |
---|---|---|
JBoss 4.0: | jbossall-client.jar | JBoss\client |
GlassFish V2: | appserv-deployment-client.jar, appserv-ext.jar, appserv-rt.jar, javaee.jar |
glassfish\lib |
Für einige andere Java EE Application Server muss stattdessen entweder eine spezielle Client-.jar-Lib erstellt werden oder eine bestimmte .jar-Lib aus dem AppServer-Verzeichnis dem Classpath hinzugefügt werden (siehe hierzu auch jee-jndi.htm#AppClient-Lib).
EJB-API
Falls Sie zu Ihrem Java EE Application Server keine Client-.jar-Lib kopiert haben, müssen Sie zumindest eine das EJB-API enthaltende .jar-Lib in das Unterverzeichnis '<MeinEjb2Projekt>\lib' kopieren, damit Sie kompilieren können, beispielsweise für WebLogic 10.3.4:
für App-Server | EJB-API-.jar-Lib | aus Verzeichnis |
---|---|---|
WebLogic 10.3.4: | javax.ejb_3.0.1.jar | WebLogic\modules |
JNDI
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' eine für den verwendeten Java EE Application Server geeignete 'jndi.properties'-Datei (wird von 'InitialContext()' benötigt) (siehe hierzu auch jee-jndi.htm#jndi.properties) (passen Sie den 'java.naming.provider.url'-Eintrag an die Adresse Ihres Java EE Application Servers an). Beispiele:
Für JBoss:
java.naming.provider.url=jnp://localhost:1099 java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces jnp.socket.Factory=org.jnp.interfaces.TimedSocketFactory
Für Oracle WebLogic:
java.naming.provider.url=t3://localhost:7001 java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
Für GlassFish V2:
org.omg.CORBA.ORBInitialHost=localhost org.omg.CORBA.ORBInitialPort=3700 java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory java.naming.factory.url.pkgs=com.sun.enterprise.naming java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
Mit Anmelde-Account:
java.naming.provider.url=... java.naming.factory.initial=... java.naming.security.principal=meinAppSrvBenutzername java.naming.security.credentials=meinAppSrvKennwort
build.properties
Speichern Sie im Verzeichnis
'<MeinEjb2Projekt>'
eine Properties-Datei 'build.properties'.
Falls Ihr Java EE Application Server eine spezielle .jar-Lib für den Client benötigt, geben Sie diese an.
Falls Ihr Java EE Application Server für Auto-Deployment konfiguriert ist, geben Sie das Auto-Deployment-Verzeichnis an.
Für JBoss zum Beispiel so:
deploy.dir=C:/JBoss/server/default/deploy
Für Oracle WebLogic zum Beispiel so:
deploy.dir=C:/WebLogic/user_projects/domains/MeineDomain/autodeploy lib.clnt.file=C:/WebLogic/wlserver_10.3/server/lib/weblogic.jar
Für GlassFish V2 zum Beispiel so:
deploy.dir=C:/GlassFish/glassfish/domains/domain1/autodeploy
(Passen Sie die Pfade an Ihre Installation an.)
Ant build.xml
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' die folgende Datei 'build.xml':
<?xml version="1.0"?> <project name="My Java EE Application Server Application" default="run-client"> <!-- Set the following three properties at command line, e.g.: ant -Dclntapp=myclientpkg.MyClientApp -Dsrvjar=MyServerApp.jar -Dsrvpkg=myserverpackage <property name="clntapp" value="..." /> <property name="srvjar" value="..." /> <property name="srvpkg" value="..." /> --> <!-- Overwrite some of the following props in a 'build.properties' file: <property name="deploy.dir" value="..." /> <property name="lib.clnt.file" value="..." /> --> <property file="build.properties" /> <property name="deploy.dir" value="dist" /> <property name="dist.dir" value="dist" /> <property name="libs.dir" value="lib" /> <property name="lib.clnt.file" value="" /> <property name="classes.dir" value="bin" /> <property name="src.dir" value="src" /> <target name="clean"> <delete dir="${dist.dir}" /> <delete dir="${classes.dir}" /> </target> <target name="compile"> <mkdir dir="${classes.dir}" /> <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="true" deprecation="true"> <classpath> <fileset dir="${libs.dir}" includes="**/*.jar" /> </classpath> </javac> </target> <target name="create-jar" depends="compile"> <mkdir dir="${dist.dir}" /> <jar jarfile="${dist.dir}/${srvjar}"> <metainf dir="${src.dir}/${srvpkg}" includes="**/*.xml" /> <fileset dir="${classes.dir}" includes="${srvpkg}/**/*.class" /> </jar> </target> <target name="deploy" depends="create-jar"> <copy todir="${deploy.dir}"> <fileset dir="${src.dir}/${srvpkg}" includes="*-service.xml" /> </copy> <copy todir="${deploy.dir}" file="${dist.dir}/${srvjar}" /> </target> <target name="undeploy"> <delete file="${deploy.dir}/${srvjar}" /> </target> <target name="run-client" depends="deploy"> <sleep seconds="7" /> <java fork="true" classname="${clntapp}"> <classpath> <fileset dir="${libs.dir}" includes="**/*.jar" /> <pathelement path="." /> <pathelement path="${classes.dir}" /> <pathelement path="${env.CLASSPATH}" /> <pathelement path="${lib.clnt.file}" /> </classpath> </java> </target> </project>
Dokumentation zur Ant-Syntax finden Sie unter http://ant.apache.org/manual/.
Log4j
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>' die folgende Datei 'log4j.properties' (weiteres zu Log4j finden Sie unter java-log4j.htm):
log4j.rootCategory=DEBUG, CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=INFO log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c{1}] %m%n
Dieses Beispiel setzt voraus, dass die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" vorbereitet sind, sowie dass JBoss, Oracle WebLogic oder GlassFish installiert ist. Für andere Java EE Application Server müsste es angepasst werden.
Sie können die meisten der im Folgenden vorgestellten Dateien auch downloaden (außer den Libs).
Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis 'src' und darin die Unterverzeichnisse 'src\meinclient' und 'src\meinsessionbeanpkg' an:
[\MeinWorkspace] '- [MeinEjb2Projekt] |- [lib] | '- ... [z.B. jbossall-client.jar] |- [src] | |- [meinclient] | '- [meinsessionbeanpkg] |- build.properties |- build.xml |- jndi.properties '- log4j.properties
EJB Home Interface
Über die 'create()'-Methode[n] im 'Home Interface' bekommt der Client Zugriff auf ein Interface zum (entfernten) EJB-Objekt auf dem Server, dem 'EJB Remote Interface'.
Anders als Stateless Session Beans dürfen Stateful Session Beans mehrere 'create()'-Methoden implementieren.
Anders als in Entity Beans existieren bei Session Beans normalerweise keine zusätzlichen Methoden zum Finden ('findBy...()').
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'IHelloWorldHome.java':
package meinsessionbeanpkg; import java.rmi.RemoteException; import javax.ejb.*; public interface IHelloWorldHome extends EJBHome { public IHelloWorld create() throws RemoteException, CreateException; public IHelloWorld create( String myname ) throws RemoteException, CreateException; }
EJB Remote Interface
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'IHelloWorld.java':
package meinsessionbeanpkg; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface IHelloWorld extends EJBObject { String sayHello() throws RemoteException; Integer getCount() throws RemoteException; }
Implementierung der Stateful Session Bean
In der Bean-Implementierung müssen alle im Remote Interface deklarierten Methoden implementiert werden und es muss zu jeder im Home Interface definierten 'create()'-Methode das passende Gegenstück mit den selben Parametern als 'ejbCreate()'-Methode geben. Bitte beachten Sie, dass die im Home Interface deklarierten 'create()'-Methoden das Remote Interface returnieren, während die 'ejbCreate()'-Implementierungen 'void' returnieren.
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'HelloWorldImpl.java':
package meinsessionbeanpkg; import javax.ejb.*; public class HelloWorldImpl implements SessionBean { private SessionContext ctx = null; private String hello = null; private int count = 0; public void ejbCreate() { hello = "Hallo Welt"; } public void ejbCreate( String myname ) { if( null == myname ) myname = "Welt"; hello = "Hallo " + myname; } public String sayHello() { count++; return hello; } public Integer getCount() { return new Integer( count ); } public void ejbActivate() {} public void ejbPassivate() {} public void ejbRemove() {} public void setSessionContext( SessionContext sessionContext ) { ctx = sessionContext; } }
EJB Deployment-Descriptor
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinsessionbeanpkg' die folgende Datei 'ejb-jar.xml':
<?xml version="1.0" encoding="UTF-8" ?> <!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> <enterprise-beans> <session> <ejb-name> HelloWorld </ejb-name> <home> meinsessionbeanpkg.IHelloWorldHome </home> <remote> meinsessionbeanpkg.IHelloWorld </remote> <ejb-class> meinsessionbeanpkg.HelloWorldImpl </ejb-class> <session-type> Stateful </session-type> <transaction-type> Container </transaction-type> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name> HelloWorld </ejb-name> <method-name> * </method-name> </method> <trans-attribute> Supports </trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
Oracle WebLogic: Zusätzlicher Deployment-Descriptor
Einige Java EE Application Server benötigen eine weitere Beschreibungsdatei.
JBoss und GlassFish V2 benötigen sie nicht, aber Oracle WebLogic benötigt sie zum Beispiel.
Falls Sie WebLogic ab Version 9.x einsetzen:
Speichern Sie im Verzeichnis
'<MeinEjb2Projekt>\src\meinsessionbeanpkg'
die folgende Datei 'weblogic-ejb-jar.xml':
<?xml version="1.0" encoding="UTF-8"?> <weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/90" xmlns:j2ee="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-ejb-jar.xsd"> <weblogic-enterprise-bean> <ejb-name> HelloWorld </ejb-name> <stateful-session-descriptor> </stateful-session-descriptor> <transaction-descriptor> <trans-timeout-seconds>30</trans-timeout-seconds> </transaction-descriptor> <enable-call-by-reference>true</enable-call-by-reference> <jndi-name> HelloWorld </jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
Falls Sie BEA WebLogic in einer niedrigeren Version als 9.x verwenden, müssen Sie den WebLogic-Deployment-Descriptor in einem anderen Format erstellen (und mit DTD statt XSD).
Client-Anwendung
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'HelloWorldApp.java':
package meinclient; import javax.naming.*; import javax.rmi.PortableRemoteObject; import meinsessionbeanpkg.IHelloWorldHome; import meinsessionbeanpkg.IHelloWorld; public class HelloWorldApp { public static void main( String[] args ) throws Exception { Context ctx = new InitialContext(); // Je nach Java EE Application Server bzw. Deployment-Descriptor: // "HelloWorld" oder // "meinsessionbeanpkg.IHelloWorldHome" Object ref = ctx.lookup( "HelloWorld" ); IHelloWorldHome home = (IHelloWorldHome) PortableRemoteObject.narrow( ref, IHelloWorldHome.class ); IHelloWorld helloWorld = home.create( (0<args.length)?args[0]:null ); System.out.println( helloWorld.sayHello() ); System.out.println( helloWorld.sayHello() ); System.out.println( helloWorld.getCount() ); } }
Verzeichnisstruktur
Die Verzeichnisstruktur muss jetzt in etwa folgendermaßen aussehen (überprüfen Sie es mit tree /F):
[\MeinWorkspace] '- [MeinEjb2Projekt] |- [lib] | '- ... [z.B. javax.ejb_3.0.1.jar, jbossall-client.jar] |- [src] | |- [meinclient] | | '- HelloWorldApp.java | '- [meinsessionbeanpkg] | |- ejb-jar.xml | |- HelloWorldImpl.java | |- IHelloWorld.java | |- IHelloWorldHome.java | '- ... eventuell weblogic-ejb-jar.xml o.a. |- build.properties |- build.xml |- jndi.properties '- log4j.properties
Wichtig: Im '<MeinEjb2Projekt>'-Verzeichnis müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.
Aufruf
Bei laufendem Java EE Application Server (JBoss, WebLogic, GlassFish, ...) öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:
cd \MeinWorkspace\MeinEjb2Projekt
ant -Dclntapp=meinclient.HelloWorldApp -Dsrvjar=HelloWorldBean.jar -Dsrvpkg=meinsessionbeanpkg
(Falls Ihr Webbrowser Zeilen umgebrochen hat: Das 'ant ...'-Kommando ist eine Kommandozeile.)
Sie erhalten die Ausgabe:
Hallo Welt
Hallo Welt
2
Falls Sie nicht Ihren Java EE Application Server für Auto-Deployment konfiguriert haben, müssen Sie zuerst das genannte 'ant'-Kommando ausführen, um die 'HelloWorldBean.jar'-Datei zu erzeugen, dann diese 'HelloWorldBean.jar'-Datei über die Administrationskonsole des Java EE Application Servers deployen und anschließend das 'ant'-Kommando erneut ausführen, um den Client aufzurufen.
Bemerkungen
Wenn Sie etwas an den Sourcen ändern und erneut 'ant ...' aufrufen, wird neu deployt und der Java EE Application Server registriert automatisch die Änderung (bei Auto-Deployment). Der Java EE Application Server braucht nicht neu gestartet zu werden.
Falls Sie eine Fehlermeldung ähnlich zu 'package javax.ejb does not exist', 'java.lang.NoClassDefFoundError' oder 'javax.naming.NoInitialContextException: Cannot instantiate class' erhalten, fehlt im 'lib'-Verzeichnis oder im Classpath eine benötigte .jar-Library (z.B. 'jbossall-client.jar', 'javax.ejb_3.0.1.jar', 'weblogic.jar', ...).
Falls Sie eine Fehlermeldung ähnlich zu 'javax.naming.NoInitialContextException: Need to specify class name ...: java.naming.factory.initial' erhalten, befindet sich das Verzeichnis von 'jndi.properties' nicht im Classpath.
Wenn Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: HelloWorld not bound' oder 'EJBException: Could not instantiate bean' erhalten, konnte der Java EE Application Server den Deploy-Vorgang nicht schnell genug abschließen. Wiederholen Sie den 'ant ...'-Aufruf einfach.
Falls Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: HelloWorld not found' erhalten, erwartet Ihr Java EE Application Server einen anderen JNDI-Namen. Probieren Sie in 'HelloWorldApp.java' beim 'ctx.lookup()'-Aufruf zum Beispiel 'meinsessionbeanpkg.IHelloWorldHome' statt 'HelloWorld'. Besser: Sehen Sie mit dem JNDI-Auslese-Programm nach, welchen JNDI-Namen Ihr Java EE Application Server vergeben hat.
Auch wenn kein Fehler auftritt:
Sehen Sie sich den JNDI-Context mit dem
JNDI-Auslese-Programm an:
Außer dem durch das Programmbeispiel generierten JNDI-Eintrag
(z.B. 'HelloWorld' oder 'meinsessionbeanpkg.IHelloWorldHome')
finden Sie noch weitere vergebene JNDI-Namen.
Falls JNDI nicht funktioniert: Fahren Sie den Java EE Application Server herunter und überprüfen Sie,
ob der JNDI-Port (z.B. für JBoss: 1099) durch ein anderes Programm belegt ist. Rufen Sie hierfür in einem Kommandozeilenfenster zum Beispiel
netstat -a, netstat -a -b -n oder telnet 127.0.0.1 1099 auf.
Je nach Java EE Application Server kann der JNDI-Port eventuell notfalls verstellt werden
(z.B. bei JBoss über die JBoss-NamingService-MBean entweder über die
JMX Console
oder die entsprechende jboss-xxx.xml-Datei).
Eclipse
Falls Sie das Projekt in Eclipse IDE for Java EE Developers ab Version 3.3 (inkl. WTP) bearbeiten wollen, richten Sie in Eclipse Ihren Java EE Application Server ein (unter 'File' | 'New' | 'Other...' | '[+] Server' | 'Server' | ...).
Falls Sie das Projekt in einer älteren Eclipse-Version bearbeiten wollen, gehen Sie wie folgt vor:
Falls Sie es nicht bereits dort angelegt haben,
kopieren Sie das Projektverzeichnis 'MeinEjb2Projekt' in Ihr Eclipse-Workspace-Verzeichnis
(z.B. 'D:\MeinWorkspace').
Wählen Sie 'File' | 'New' | 'Project...' | 'Java Project' | 'Project Name = MeinEjb2Projekt' | 'Next'.
Falls zugeklappt: Klappen Sie mit Klick auf das kleine schwarze Dreieck die 'Details' auf.
Wählen Sie unter 'Details' die Option 'Add project 'MeinEjb2Projekt' to build path'.
Achten Sie darauf, dass als 'Default output folder' eingestellt ist: 'MeinEjb2Projekt/bin'.
Klicken Sie auf den Tabulatorreiter 'Libraries', wählen Sie 'Add External Jars...' und das
'lib'-Verzeichnis,
markieren Sie darin alle Dateien und wählen Sie 'Öffnen' | 'Finish'.
Klicken Sie im 'Package Explorer' auf das [+] vor 'MeinEjb2Projekt' und
mit der rechten Maustaste auf 'build.xml' | 'Run As' | 'Ant Build...'.
Achten Sie darauf, dass unter dem Tabulatorreiter 'Targets' in der Target-Liste 'run-client' angewählt ist.
Schalten Sie um auf den Tabulatorreiter 'Properties',
schalten Sie die Option 'Use global properties as specified in the Ant runtime preferences' aus und
fügen Sie mit 'Add Property...' drei Properties hinzu:
'clntapp=meinclient.HelloWorldApp', 'srvjar=HelloWorldBean.jar' und 'srvpkg=meinsessionbeanpkg'.
Klicken Sie auf 'Run'.
Falls Sie nicht deployen wollen, sondern nur den Client ändern und aufrufen wollen, genügt auch, wenn Sie die Java-Sourcedatei 'HelloWorldApp' (mit der 'main()'-Methode) öffnen, den Fokus und Textcursor in diese Datei setzen und wählen: 'Run' | 'Run as' | 'Java Application'.
Normalerweise werden Entity Beans von Session Beans aufgerufen und nicht von externen Clients. Um es einfacher zu halten, wird im folgenden Demobeispiel trotzdem die Entity Bean direkt von einem externen Client aufgerufen. Bitte beachten Sie, dass dies in einigen neueren Java EE Application Servern (z.B. WebLogic 10.3.4) nicht mehr funktioniert.
Dieses Beispiel setzt voraus, dass die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" vorbereitet sind, sowie dass JBoss 4.x oder BEA WebLogic 9.2 installiert ist. Für andere Java EE Application Server müsste es angepasst werden.
EJB Home Interface
Entity Beans müssen eindeutige Primary-Key-Werte haben, über die sie gefunden werden können (analog zu Primary Keys in relationalen Datenbanken), auch wenn als Speichermedium keine relationale Datenbank eingesetzt wird. Wird der Primary-Key-Wert als Objekt realisiert, muss darauf geachtet werden, dass das Objekt serialisierbar sein muss und funktionierende 'hashCode()'- und 'equals()'-Methoden haben muss.
Anders als in Session Beans müssen bei Entity Beans im Home Interface zusätzlich zur 'create()'-Methode zum Erzeugen der Bean weitere Methoden zum Finden der Bean deklariert werden, zumindest 'findByPrimaryKey()'. Alle Finder-Methoden müssen mit 'find...' beginnen.
Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis 'src\meinentitybeanpkg' an und speichern Sie darin die folgende Datei 'IKundeHome.java':
package meinentitybeanpkg; import java.util.Collection; import java.rmi.RemoteException; import javax.ejb.*; public interface IKundeHome extends EJBHome { public IKunde create() throws RemoteException, CreateException; public IKunde findByPrimaryKey( Integer kundennummer ) throws RemoteException, FinderException; public Collection findByName( String name ) throws RemoteException, FinderException; }
EJB Remote Interface
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'IKunde.java':
package meinentitybeanpkg; import java.util.Date; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface IKunde extends EJBObject { Integer getKundennummer() throws RemoteException; String getName() throws RemoteException; void setName( String name ) throws RemoteException; Date getDatum() throws RemoteException; void setDatum( Date datum ) throws RemoteException; }
Implementierung der CMP 2.0 Entity Bean
Seit CMP 2.0 muss die Implementierung einer CMP Entity Bean eine abstrakte Klasse sein, in der für die persistenten Felder keine Elementvariablen und nur abstrakte Getter- und Setter-Methoden definiert werden.
In der Bean-Implementierung müssen alle im Remote Interface deklarierten Methoden implementiert werden und
es muss zu jeder im Home Interface definierten 'create()'-Methode
das passende Gegenstück mit den selben Parametern als 'ejbCreate()'-Methode geben.
Bitte beachten Sie, dass die im Home Interface deklarierten 'create()'- und 'find...()'-Methoden
das Remote Interface oder eine Collection davon returnieren,
während in der Bean-Implementierung bei den 'ejbCreate()'-Methoden
als Rückgabewert ein Primary-Key-Objekt oder eine Collection davon deklariert wird.
Eine weitere Besonderheit ist, dass in CMP-Beans die 'ejbCreate()'-Methoden 'null' returnieren
(die Deklaration des Rückgabewerts besteht aus Kompatibilitätsgründen zu früheren EJB-Versionen).
Die im Home Interface deklarierte 'findByPrimaryKey()'-Methode darf in der CMP-Bean nicht implementiert werden, sie wird vom Container zur Verfügung gestellt. Andere 'find...()'-Methoden werden normalerweise nicht ausprogrammiert, sondern in der 'ejb-jar.xml' als 'EJBQL'-Abfrage formuliert (EJB Query Language) (siehe unten 'ejb-jar.xml').
Die hier vorgestellte Bean-Implementierung erzeugt neue Primary Keys über die zusätzliche Datenbanktabelle
'kunde_id_seq' über die Methode 'getNextId()'.
Je nach Datenbank gibt es hierfür auch wesentlich elegantere Wege.
Bei EJB Version 3.0 kann die automatische Generierung durch folgende Annotation erreicht werden:
'@Id(generate=GeneratorType.AUTO)'.
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'KundeImpl.java':
package meinentitybeanpkg; import java.util.Date; import java.sql.*; import javax.sql.*; import javax.ejb.*; import javax.naming.*; public abstract class KundeImpl implements EntityBean { private EntityContext ctx; public abstract Integer getKundennummer(); public abstract void setKundennummer( Integer kundennummer ); public abstract String getName(); public abstract void setName( String name ); public abstract Date getDatum(); public abstract void setDatum( Date datum ); public Integer ejbCreate() throws CreateException { try { setKundennummer( new Integer( getNextId() ) ); return null; } catch( SQLException ex ) { throw new CreateException( ex.toString() ); } } public void ejbPostCreate() {} public void ejbActivate() {} public void ejbPassivate() {} public void ejbLoad() {} public void ejbStore() {} public void ejbRemove() {} public void setEntityContext( EntityContext entityContext ) { ctx = entityContext; } public void unsetEntityContext() { ctx = null; } protected int getNextId() throws SQLException { final String sErr = "Fehler: Kein neuer Primary Key erzeugbar. "; Connection conn = null; try { InitialContext context = new InitialContext(); // Je nach Java EE Application Server und DataSource-Definition: // JBoss: "java:/MeinDatasourceJndiName" // WebLogic: "jdbc/MeinDatasourceJndiName" // andere event.: "java:comp/env/jdbc/MeinDatasourceJndiName" DataSource ds = (DataSource) context.lookup( "java:/MeinDatasourceJndiName" ); conn = ds.getConnection(); PreparedStatement ps; try { ps = conn.prepareStatement( "UPDATE kunde_id_seq SET next_kunde_id = next_kunde_id + 1" ); if( 1 != ps.executeUpdate() ) throw new SQLException(); } catch( SQLException ex ) { Statement st = conn.createStatement(); st.execute( "CREATE TABLE kunde_id_seq( next_kunde_id INTEGER NOT NULL );" ); st.execute( "INSERT INTO kunde_id_seq VALUES ( 0 );" ); st.close(); ps = conn.prepareStatement( "UPDATE kunde_id_seq SET next_kunde_id = next_kunde_id + 1" ); if( 1 != ps.executeUpdate() ) throw new SQLException( sErr ); } ps.close(); ps = conn.prepareStatement( "SELECT next_kunde_id FROM kunde_id_seq" ); ResultSet rs = ps.executeQuery(); if( rs.next() ) return rs.getInt( "next_kunde_id" ); throw new SQLException( sErr ); } catch( NamingException ex ) { throw new SQLException( sErr + ex.toString() ); } finally { try { if( null != conn ) conn.close(); } catch( Exception ex ) {/*ok*/} } } }
Passen Sie die Zeile 'DataSource ds = (DataSource) context.lookup( "java:/MeinDatasourceJndiName" );' an Ihren Java EE Application Server und Ihre DataSource-Definition an.
Bitte beachten Sie, dass die hier gezeigte Variante der Primary-Key-Generierung nur der Einfachheit halber gewählt wurde und nicht für eine Mehrbenutzer-Umgebung geeignet ist (verwenden Sie stattdessen eine Datenbank-eigene Sequenz).
Standard EJB Deployment-Descriptor
Vom eingesetzten Java EE Application Server unabhängige Einstellungen erfolgen im Standard EJB Deployment-Descriptor.
Sollen zusätzlich zur obligatorischen 'findByPrimaryKey()'-Methode weitere 'find...()'-Methoden implementiert werden, werden im EJB Deployment-Descriptor die dazu notwendigen SQL-Abfrage-Strings definiert, und zwar in 'EJBQL' (EJB Query Language). Im folgenden Beispiel ist es die Methode 'findByName()' mit dem EJBQL-String 'SELECT OBJECT(k) FROM Kunde AS k WHERE k.name like ?1', der als CDATA (nicht zu parsende 'Character-Daten') deklariert ist.
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'ejb-jar.xml':
<?xml version="1.0" encoding="UTF-8" ?> <!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> <enterprise-beans> <entity> <display-name> Kunde </display-name> <ejb-name> Kunde </ejb-name> <home> meinentitybeanpkg.IKundeHome </home> <remote> meinentitybeanpkg.IKunde </remote> <ejb-class> meinentitybeanpkg.KundeImpl </ejb-class> <persistence-type> Container </persistence-type> <prim-key-class> java.lang.Integer </prim-key-class> <reentrant> false </reentrant> <cmp-version> 2.x </cmp-version> <abstract-schema-name> Kunde </abstract-schema-name> <cmp-field><field-name> kundennummer </field-name></cmp-field> <cmp-field><field-name> name </field-name></cmp-field> <cmp-field><field-name> datum </field-name></cmp-field> <primkey-field> kundennummer </primkey-field> <resource-ref> <res-ref-name> jdbc/MeinDatasourceJndiName </res-ref-name> <res-type> javax.sql.DataSource </res-type> <res-auth> Container </res-auth> </resource-ref> <query> <query-method> <method-name> findByName </method-name> <method-params> <method-param> java.lang.String </method-param> </method-params> </query-method> <ejb-ql> <![CDATA[SELECT OBJECT(k) FROM Kunde AS k WHERE k.name like ?1]]> </ejb-ql> </query> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name> Kunde </ejb-name> <method-name> * </method-name> </method> <trans-attribute> Required </trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
Spezifische Deployment-Descriptoren
Vom Java EE Application Server abhängige Einstellungen erfolgen in Extra-Deployment-Descriptoren. Einige Java EE Application Server können auch ohne weitere Deployment-Descriptoren auskommen und Standardeinstellungen verwenden. Zum Beispiel würde JBoss seine integrierte hSqlDb-Datenbank einsetzen. In diesem Beispiel soll jedoch die oben definierte DataSource verwendet werden.
JBoss CMP-JDBC-Deployment-Descriptor
Falls Sie JBoss verwenden:
Legen Sie im Verzeichnis
'<MeinEjb2Projekt>\src\meinentitybeanpkg'
die folgende Datei 'jbosscmp-jdbc.xml' an:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jbosscmp-jdbc PUBLIC "-//JBoss//DTD JBOSSCMP-JDBC 3.0//EN" "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_0.dtd"> <jbosscmp-jdbc> <enterprise-beans> <entity> <ejb-name> Kunde </ejb-name> <datasource> java:/MeinDatasourceJndiName </datasource> <datasource-mapping> mySQL </datasource-mapping> <table-name> Kunde </table-name> </entity> </enterprise-beans> </jbosscmp-jdbc>
Oracle WebLogic EJB- und CMP-RDBMS-Deployment-Descriptor
Falls Sie Oracle WebLogic ab Version 9.x einsetzen:
Legen Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'weblogic-ejb-jar.xml' an:
<?xml version="1.0" encoding="UTF-8"?> <weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/90" xmlns:j2ee="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-ejb-jar.xsd"> <weblogic-enterprise-bean> <ejb-name>Kunde</ejb-name> <entity-descriptor> <entity-cache> <max-beans-in-cache>1000</max-beans-in-cache> <read-timeout-seconds>600</read-timeout-seconds> <concurrency-strategy>Database</concurrency-strategy> </entity-cache> <persistence> <persistence-use> <type-identifier>WebLogic_CMP_RDBMS</type-identifier> <type-version>6.0</type-version> <type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage> </persistence-use> </persistence> </entity-descriptor> <transaction-descriptor> <trans-timeout-seconds>30</trans-timeout-seconds> </transaction-descriptor> <enable-call-by-reference>true</enable-call-by-reference> <jndi-name>Kunde</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
Legen Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinentitybeanpkg' die folgende Datei 'weblogic-cmp-rdbms-jar.xml' an:
<?xml version="1.0" encoding="UTF-8"?> <weblogic-rdbms-jar xmlns="http://www.bea.com/ns/weblogic/90" xmlns:j2ee="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-rdbms20-persistence.xsd"> <weblogic-rdbms-bean> <ejb-name>Kunde</ejb-name> <data-source-jndi-name>jdbc/MeinDatasourceJndiName</data-source-jndi-name> <table-map> <table-name>Kunde</table-name> <field-map> <cmp-field>kundennummer</cmp-field> <dbms-column>kundennummer</dbms-column> </field-map> <field-map> <cmp-field>name</cmp-field> <dbms-column>name</dbms-column> </field-map> <field-map> <cmp-field>datum</cmp-field> <dbms-column>datum</dbms-column> </field-map> </table-map> </weblogic-rdbms-bean> </weblogic-rdbms-jar>
Falls Sie BEA WebLogic in einer niedrigeren Version als 9.x verwenden, müssen Sie die beiden WebLogic-Deployment-Descriptoren in einem anderen Format erstellen (und mit DTD statt XSD).
Client-Anwendung
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'EntityApp.java':
package meinclient; import java.util.*; import java.text.SimpleDateFormat; import javax.naming.*; import javax.rmi.PortableRemoteObject; import meinentitybeanpkg.IKundeHome; import meinentitybeanpkg.IKunde; public class EntityApp { public static void main( String[] args ) throws Exception { SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" ); Context ctx = new InitialContext(); // Je nach Java EE Application Server bzw. Deployment-Descriptor: // "Kunde" oder // "meinentitybeanpkg.IKundeHome" Object ref = ctx.lookup( "Kunde" ); IKundeHome home = (IKundeHome) PortableRemoteObject.narrow( ref, IKundeHome.class ); // Kunde neu anlegen: IKunde kunde = home.create(); kunde.setName( "Hans Otto" ); kunde.setDatum( new Date() ); System.out.println( "Kunde neu angelegt: Nr. " + kunde.getKundennummer() + ", " + kunde.getName() + ", Datum " + df.format( kunde.getDatum() ) ); // Bestimmte Kunden auslesen: System.out.println( "\nKunden mit 'Ott' im Namen:" ); Collection coll = home.findByName( "%Ott%" ); Iterator it = coll.iterator(); while( it.hasNext() ) { kunde = (IKunde) it.next(); System.out.println( "\nKundennummer : " + kunde.getKundennummer() ); System.out.println( "Name : " + kunde.getName() ); System.out.println( "Datum : " + df.format( kunde.getDatum() ) ); } } }
Verzeichnisstruktur
Die Verzeichnisstruktur muss jetzt in etwa folgendermaßen aussehen (überprüfen Sie es mit tree /F):
[\MeinWorkspace] '- [MeinEjb2Projekt] |- [bin] | '- ... |- [lib] | '- ... [z.B. javax.ejb_3.0.1.jar, jbossall-client.jar] |- [src] | |- [meinclient] | | |- EntityApp.java | | '- HelloWorldApp.java | |- [meinentitybeanpkg] | | |- ejb-jar.xml | | |- IKunde.java | | |- IKundeHome.java | | |- KundeImpl.java | | '- jbosscmp-jdbc.xml [bzw. weblogic-cmp-rdbms-jar.xml und weblogic-ejb-jar.xml] | '- [meinsessionbeanpkg] | |- ejb-jar.xml | |- HelloWorldImpl.java | |- IHelloWorld.java | |- IHelloWorldHome.java | '- ... eventuell weblogic-ejb-jar.xml o.a. |- build.properties |- build.xml |- jndi.properties '- log4j.properties
Wichtig: Im '<MeinEjb2Projekt>'-Verzeichnis müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.
Aufruf
Bei laufendem Java EE Application Server (JBoss, WebLogic, ...) öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:
cd \MeinWorkspace\MeinEjb2Projekt
ant -Dclntapp=meinclient.EntityApp -Dsrvjar=KundeEntityBean.jar -Dsrvpkg=meinentitybeanpkg
ant -Dclntapp=meinclient.EntityApp -Dsrvjar=KundeEntityBean.jar -Dsrvpkg=meinentitybeanpkg
Sie erhalten die Ausgabe:
Kunde neu angelegt: Nr. 2, Hans Otto, Datum 2005-02-28
Kunden mit 'Ott' im Namen:
Kundennummer : 1
Name : Hans Otto
Datum : 2005-02-28
Kundennummer : 2
Name : Hans Otto
Datum : 2005-02-28
Wenn Sie eine Fehlermeldung ähnlich zu 'NameNotFoundException: Kunde not bound' oder 'EJBException: Could not instantiate bean' erhalten, konnte der Java EE Application Server den Deploy-Vorgang nicht schnell genug abschließen. Wiederholen Sie den 'ant ...'-Aufruf einfach.
Eclipse
Falls Sie das Projekt in
Eclipse
bearbeiten wollen, gehen Sie vor wie
oben beschrieben.
Falls Sie außerhalb von Eclipse Dateien verändert oder hinzugefügt haben,
müssen Sie in Eclipse die entsprechenden Projekte im 'Package Explorer' markieren und mit 'F5' einen 'Refresh' durchführen.
Außerdem müssen Sie für dieses Beispiel die build.xml-Properties anders einstellen:
'clntapp=meinclient.EntityApp', 'srvjar=KundeEntityBean.jar' und 'srvpkg=meinentitybeanpkg'.
Sehen Sie sich den JNDI-Context mit dem
JNDI-Auslese-Programm an:
Dort finden Sie den durch das Programmbeispiel generierten JNDI-Eintrag 'Kunde'.
Sie können sich das Ergebnis in der MySQL-Datenbank zum Beispiel mit 'SQuirreL' ansehen.
Informationen zum JMS (Java Message Service) finden Sie in jee-jms.htm.
Dieses Beispiel setzt die oben aufgeführten "Gemeinsamen Dateien für die Beispiele" und JBoss voraus. Für andere Java EE Application Server müsste es angepasst werden.
Implementierung der MDB (Message Driven Bean)
Da Message Driven Beans nicht direkt von Clients aufgerufen werden, benötigen Sie keine 'Home' und 'Remote Interfaces'.
Im Folgenden wird eine MDB vorgestellt, die Nachrichten eines JMS-'Topics' abruft.
Legen Sie in Ihrem Projektverzeichnis '<MeinEjb2Projekt>' das Unterverzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' an und speichern Sie darin die folgende Datei 'WetterMDB.java':
package meinmdbpkg; import javax.ejb.*; import javax.jms.*; public class WetterMDB implements MessageDrivenBean, MessageListener { private MessageDrivenContext ejbContext; public void onMessage( Message message ) { try { MapMessage mapMessage = (MapMessage) message; System.out.println( "WetterMDB: Wl=" + mapMessage.getString( "Wetterlage" ) + " Wr=" + mapMessage.getString( "Windrichtung" ) + " T=" + mapMessage.getDouble( "Temperatur" ) + "C" ); message.acknowledge(); } catch( Exception ex ) { System.out.println( ex.getMessage() ); } } public void ejbCreate() {} public void ejbRemove() { ejbContext = null; } public void setMessageDrivenContext( MessageDrivenContext messageDrivenContext ) { this.ejbContext = messageDrivenContext; } }
JMS-Topic-Service
JMS-Queues und -Topics müssen beim JMS-Provider angemeldet werden, indem entsprechende '*-service.xml'-Dateien in das JBoss-Deploy-Verzeichnis kopiert werden (hierfür ist in der 'build.xml' beim 'deploy'-Target der '<copy ... "*-service.xml" ...'-Ausdruck).
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'wetter-service.xml':
<?xml version="1.0" encoding="UTF-8"?> <!-- Destination without a configured SecurityManager or without a SecurityConf will default to role guest with read=true, write=true, create=false. --> <server> <mbean code="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=WetterTopic"> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> <depends optional-attribute-name="SecurityManager"> jboss.mq:service=SecurityManager </depends> </mbean> </server>
Standard EJB Deployment-Descriptor
Von der vorhandenen JMS-Implementierung unabhängige Einstellungen (z.B. ob JMS Queue oder JMS Topic) erfolgen im Standard EJB Deployment-Descriptor.
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'ejb-jar.xml':
<?xml version="1.0" encoding="UTF-8"?> <!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> <enterprise-beans> <message-driven> <ejb-name> WetterMDB </ejb-name> <ejb-class> meinmdbpkg.WetterMDB </ejb-class> <transaction-type> Container </transaction-type> <acknowledge-mode> Auto-acknowledge </acknowledge-mode> <message-driven-destination> <destination-type> javax.jms.Topic </destination-type> <subscription-durability> NonDurable </subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> </ejb-jar>
JBoss MDB Deployment-Descriptor
Vom JMS-Provider abhängige Einstellungen (z.B. die Verknüpfung mit einem bestimmten Topic per JNDI-Namen) erfolgen in einem Extra-Deployment-Descriptor.
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinmdbpkg' die folgende Datei 'jboss.xml':
<?xml version="1.0"?> <jboss> <enterprise-beans> <message-driven> <ejb-name> WetterMDB </ejb-name> <configuration-name> Standard Message Driven Bean </configuration-name> <destination-jndi-name> topic/WetterTopic </destination-jndi-name> </message-driven> </enterprise-beans> </jboss>
JMS-Nachrichtensender
Speichern Sie im Verzeichnis '<MeinEjb2Projekt>\src\meinclient' die folgende Datei 'MdbApp.java':
package meinclient; import javax.jms.*; import javax.naming.*; public class MdbApp { public static void main( String[] args ) throws NamingException, JMSException { final String[] WETTERLAGEN = { "Sonnig", "Wolkig", "Regen " }; final String[] WINDRICHTUNGEN = { "Nord", "West", "Sued", "Ost " }; final String WETTER_TOPIC = "topic/WetterTopic"; Context ctx = null; TopicConnection connect = null; TopicSession session = null; Topic topic = null; TopicPublisher sender = null; try { ctx = new InitialContext(); TopicConnectionFactory fact = (TopicConnectionFactory) ctx.lookup( "ConnectionFactory" ); connect = fact.createTopicConnection(); session = connect.createTopicSession( false, Session.AUTO_ACKNOWLEDGE ); try { topic = (Topic) ctx.lookup( WETTER_TOPIC ); } catch( NameNotFoundException ex ) { topic = session.createTopic( WETTER_TOPIC ); ctx.bind( WETTER_TOPIC, topic ); } sender = session.createPublisher( topic ); connect.start(); for( int i=0; i<10; i++ ) { int rndm = (int) (Math.random() * 300); String wetterlage = WETTERLAGEN[rndm % WETTERLAGEN.length]; String windrichtung = WINDRICHTUNGEN[rndm % WINDRICHTUNGEN.length]; double temperatur = (double) (rndm - 50) / 10; MapMessage msg = session.createMapMessage(); msg.setString( "Wetterlage", wetterlage ); msg.setString( "Windrichtung", windrichtung ); msg.setDouble( "Temperatur", temperatur ); sender.publish( msg ); System.out.println( "Sending Wl=" + wetterlage + " Wr=" + windrichtung + " T=" + temperatur + "C" ); System.out.println( "Sending " + msg.toString() ); } } finally { try { if( null != sender ) sender.close(); } catch( Exception ex ) {/*ok*/} try { if( null != session ) session.close(); } catch( Exception ex ) {/*ok*/} try { if( null != connect ) connect.close(); } catch( Exception ex ) {/*ok*/} try { if( null != ctx ) ctx.close(); } catch( Exception ex ) {/*ok*/} } } }
Im Verzeichnis '<MeinEjb2Projekt>' müssen sich die Dateien 'build.properties', 'build.xml', 'jndi.properties' und 'log4j.properties' befinden.
Bei laufendem JBoss öffnen Sie ein Kommandozeilenfenster und geben die folgenden Kommandos ein:
cd \MeinWorkspace\MeinEjb2Projekt
ant -Dclntapp=meinclient.MdbApp -Dsrvjar=WetterMDB.jar -Dsrvpkg=meinmdbpkg
In diesem Kommandozeilenfenster erhalten Sie die Ausgabe der gesendeten Wetternachrichten. In dem Kommandozeilenfenster von JBoss sehen Sie die empfangenen Wetternachrichten.
Mit etwas Glück können Sie vielleicht beobachten, dass die empfangenen Nachrichten nicht immer in der Reihenfolge der gesendeten Nachrichten erscheinen.
Sehen Sie sich den JNDI-Context mit dem
JNDI-Auslese-Programm an:
Unter 'topic' finden Sie den durch das MDB-Programmbeispiel generierten JNDI-Eintrag 'topic/WetterTopic'.