Ressourcen in Java EE Umgebungen
Java EE Komponenten verwenden externe Ressourcen niemals direkt sondern in abstrakter Form adressiert über einen logischen Namen. Der Bedarf für eine solche Ressource und ihr logischer Name werden im Deploymentdeskriptor deklariert. Im Rahmen des Deployments der Anwendung werden dann diese logischen Namen mit den passenden physikalischen Ressourcen verknüpft. Dieser zentrale Mechanismus basiert auf dem Java Naming and Directory Interface. Dieses Vorgehen garantiert die Portabilität der Java EE Komponente und macht sie konfigurierbar - Anpassungen der Umgebung sind ohne Änderung im Code oder in den Deskriptoren der Anwendung möglich.
JNDI Contexte in Java EE Servern
Jede Java EE Komponenten adressiert Ressourcen über eine (logische) JNDI Contextwurzel:
java:comp/envJNDI Lookups ohne diesen Präfix werden vom Server als Fehler oder in serverspezifischer Weise interpretiert und führen zu nicht portablen Anwendungen. Um das zu unterstreichen kann folgendes Programmieridiom verwendet werden:
// impliziter Initial Context InitialContext ictx = new InitialContext(); // logischer JNDI Rootcontext der Serverkomponente, kann mehrfach verwendet werden Context ctx = (Context) ictx.lookup("java:comp/env"); // weiter mit Ressourcenpfad Object petShopDB = ctx.lookup("jdbc/pets/PetShopDB"); :Die logischen Contextpfade adressieren üblicherweise eine zweistufige Hierarchie, die den Namen der Anwendung gefolgt von einem Resource-Alias beinhaltet. Der vollständige logische JNDI Pfad hat dann diese Struktur:
java:comp/env/<resourcetype>/<resource-alias>Die Spezifikation gibt Empfehlungen für die verschiedenen Ressourcentypen.
Resource Manager | Connection Factory Type | JNDI Contextpfad |
---|---|---|
JDBC | javax.sql.DataSource | java:comp/env/jdbc/[pfad] |
JMS | javax.jms.TopicConnectionFactory, javax.jms.QueueConnectionFactory | java:comp/env/jms/[pfad] |
JavaMail | javax.mail.Session | java:comp/env/mail/[pfad] |
URL | java.net.URL | java:comp/env/url/[pfad] |
JAXR ResourceAdapter | javax.xml.registry.ConnectionFactory | java:comp/env/eis/JAXR/[pfad] |
JCA Outbound Resource Adapter | a.b.c.MyConnectionFactory | java:comp/env/eis/[pfad] |
<resource-ref> <res-ref-name>jdbc/petShopDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
<resource-env-ref> <resource-env-ref-name>jms/pets/stockQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref>
<!-- Remote --> <ejb-ref> <ejb-ref-name>ejb/pets/abc</ejb-ref-name> <ejb-ref-type>session</ejb-ref-type> <home>de.pets.ABCHome</home> <remote>de.pets.ABC</remote> </ejb-ref>Hinweis: Referenzen auf remote Beanstubs dürfen nicht durch einen einfachen Cast typisiert werden:
InitialContext ictx = new InitialContext(); Context ctx = (Context) ictx.lookup("java:comp/env"); Object ejbHome = ctx.lookup("ejb/pets/AccountEJB"); // falsch! accountHome = (AccountHome) ejbHome; // richtig: accountHome = (AccountHome) javax.rmi.PortableRemoteObject.narrow(ejbHome, AccountHome.class); :Die Deklaration von Locale Interfaces geschieht mit dem ejb-locale-ref Element:
<!-- Locale --> <ejb-local-ref> <ejb-ref-name>ejb/pets/efg</ejb-ref-name> <ejb-ref-type>session</ejb-ref-type> <local-home>de.pets.LocalEFGHome</local-home> <local>de.pets.LocalEFG</local> </ejb-local-ref>
<env-entry> <env-entry-name>pets/maxvalue</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>665</env-entry-value> </env-entry>In EJB Containern sind als Typen java.lang.String und alle Wrapper-Klassen (Integer, Boolean etc.) erlaubt. In Webcontainern sind alle Klassen erlaubt, die einen Konstruktor mit String-Parameter besitzen.
Context ic = new InitialContext(); UserTransaction utx = (UserTransaction) ic.lookup("java:comp/UserTransaction"); utx.begin(); ... utx.commit();Alternativ wird die javax.transaction.UserTransaction auch über den EJB-Context bereitgestellt:
UserTransaction utx = ejbContext.getUserTransaction(); utx.begin(); ... utx.commit();
Die Bereitstellung von Ressourcen
Wie beschrieben werden im Kontext einer Anwendung Ressourcen logisch adressiert, im Deploymentdescriptor (ra.xml, web.xml, ejb-jar.xml) werden diese Namen deklariert. Die Details sind spezifiziert und immer gleich.
Nun fehlen noch folgende Schritte, um eine Ressource in einer Serverumgebung bereitzustellen:
Im Tomcat kann man im Kontext der Webanwendung eine DataSource deklarieren. Sie ist dann schon mit dem "richtigen" logischen Namen ausgestattet aber nur für diese Anwendung verfügbar. In der /META-INF/context.xml
<Context ...> ... <Resource name="jdbc/petDatastore" auth="Container" type="javax.sql.DataSource" username="dbusername" password="dbpassword" driverClassName="org.hsql.jdbcDriver" url="jdbc:HypersonicSQL:database" maxActive="8" maxIdle="4"/> ... </Context>Im Tomcat kann eine DataSource auch global (für den ganzen Server) verfügbar gemacht werden. Dazu ist in der CATALINA/conf/server.xml ein entsprechender GlobalNamingResources Eintrag zu machen. Nun muss aber in der /META-INF/context.xml der Anwendung ein ResourceLink Eintrag den globalen Namen mit dem logischen der Anwendung verknüpfen.
Im Orion kann global eine Datasource verfügbar gemacht werden
<data-sources> <data-source name="vendor" location="jdbc/petDatastore" class="a.b.SomeDatabase" username="sa" password="***" host="localhost" schema="database-schemas/ms-sql.xml"> <property name="databaseName" value="sample"/> </data-source> </data-sources>Hier ist jdbc/petDatastore dann der JNDI Name und es muss mit einem resource-ref-mapping Eintrag in der orion-ejb.xml / orion-web.xml dieser JNDI Name mit dem logischen Namen der Anwendung verknüpft werden.
Im Glassfish nutzt man die Admin-Konsole um eine Datasource einzurichten. Anschließend verknüpft man diese mit einem JNDI-Namen (zB. jdbc/petDatastore), der dann auch überraschenderweise in der Anwendung unter java:comp/env/jdbc/petDatastore erreichbar ist. Besser (weil portabel): man wählt in der Anwendung einen logischen Namen und Verknüpft in der WEB-INF/sun-web.xml diesen mit dem eigentlichen JNDI Namen, etwa so:
<resource-ref> <res-ref-name>jdbc/petApp</res-ref-name> // logischer Name <jndi-name>jdbc/petDatastore</jndi-name> </resource-ref>
Portable Java EE Komponente müssen Ressourcen mit logischen Namen referenzieren. Diese Namen beginnen mit java:comp/env und folgen dann einer Konvention die hier beschrieben wurde. Alle von einer Java EE Komponente geforderten Ressourcen und deren logische Namen werden in ihrem Deploymentdeskriptor deklariert. Die Java EE Spezifikation regelt alle Details. Unter keinen Umständen sollte eine Komponente die Ressourcen ihrer Umgebung "direkt" vom JNDI des Servers holen, der JNDI Pfad muss also immer mit java:comp/env beginnen und es muss immer der Standard InitialContext benutzt werden.
Im Rahmen der Serveradministration und des Anwendungsdeployments werden dann serverspezifisch diese Ressourcen konfiguriert und bereitgestellt. Oftmals hat man dann noch die Wahl, ob eine Ressource im Kontext der Anwendnung oder global für den gesamten Server zur Verfügung stehen soll. Diese Ressourcen werden außerdem im JNDI des Servers eingetragen. Schließlich muss, ebenfalls serverspezifisch, im Kontext der Anwendung der logische Name der Ressource mit ihrem JNDI Namen verknüpft werden. Dieser Schritt kann nur dann entfallen, wenn diese Verknüpfung implizit schon gegeben ist, zum Beispiel weil die Ressource im Kontext der Anwendung bereitgestellt wird.
Java Naming and Directory Interface (JNDI)
Blueprints - Guidelines, Patterns, and code for end-to-end Java applications