Start - Publikationen - Wissen - TOGAF - Impressum -

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/env
JNDI 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.
  1. Connection factory administered objects: Hier handelt es sich um Ressourcen, die Factories für Connections repräsentieren. Diese Connection werden gewöhnlich über vom Treiber implementierte und vom Container verwaltete Pools bereitgestellt:
    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]
    Die Deklaration im Deploymentdescriptor erfolgt mit dem resource-ref Element:
    <resource-ref>
      <res-ref-name>jdbc/petShopDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
    </resource-ref>
    
  2. Destination administered objects: das sind direkt adressierbare (zustandslose) Ressourcen. Jeder Container muss JMS Queues und JMS Topics standardmäßig als administered objects verfügbar machen, sie sollen unter java:comp/env/jms erreichbar sein. Die Deklaration im Deploymentdescriptor erfolgt mit dem resource-env-ref Element:
    <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>
    
  3. Enterprise Java Beans werden im Subcontext java:comp/env/ejb abgelegt. Die Deklaration im Deploymentdescriptor erfolgt mit dem ejb-ref Element:
    <!-- 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>
    
  4. Umgebungsvariablen werden in einem beliebigen Subcontext adresssiert. Die Deklaration im Deploymentdescriptor erfolgt mit dem env-entry Element:
    <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.
  5. Der Transaction Service (javax.transaction.UserTransaction) besitzt folgenden, von der Spezifikation fest vorgegebenen logischen JNDI Namen: java:comp/UserTransaction.
    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:

  1. Der Treiber der Ressource muss konfiguriert werden.
  2. Die Ressource muss einen JNDI Namen ("Location") bekommen.
  3. Der JNDI Name der Ressource muss im Kontext der Anwendung mit dem logischen Namen der Ressource verknüpft werden.
Diese drei Schritte erfolgen serverspezifisch! Beispiele machen das klarer (eine DataSource soll in einer Webanwendung verfügbar gemacht werden):

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>

Fazit


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.

Referenzen


Java Naming and Directory Interface (JNDI)
Blueprints - Guidelines, Patterns, and code for end-to-end Java applications

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved