← Zur Uebersicht

JNDI wirklich verstehen

Dieses Dokument ist bewusst eigenstaendig und langsamer als docs/10-ejb-konzepte/07-jndi-und-packaging.md - dort wird JNDI vorausgesetzt und im Zusammenspiel mit Deployment-Deskriptoren behandelt. Hier geht es nur um die eine Frage: Was ist JNDI eigentlich, und was passiert bei einem Lookup wirklich?

Die Analogie: ein Telefonbuch fuer Objekte

Stell dir vor, dein Code braucht eine Datenbankverbindung. Zwei Moeglichkeiten:

  1. Direkt: Der Code enthaelt die JDBC-URL, Benutzername und Passwort fest im Quelltext oder in einer eigenen Konfigurationsdatei, die er selbst einliest.
  2. Ueber ein Telefonbuch: Der Code fragt einen zentralen Dienst: "Gib mir das Ding, das unter dem Namen jdbc/novaris/policyDS eingetragen ist." Der Dienst gibt eine fertig konfigurierte, einsatzbereite Datenbankverbindung zurueck - der Code selbst weiss nicht und muss nicht wissen, welcher Server, welcher Port, welches Passwort dahintersteckt.

JNDI (Java Naming and Directory Interface) ist genau dieses Telefonbuch - ein Java-Standard-API, um Objekte anhand eines Namens (statt einer direkten Referenz) zu finden. Der Anwendungsserver (WebSphere, Liberty, ...) fuehrt dieses Telefonbuch und traegt beim Start Eintraege ein: "der Name jdbc/novaris/policyDS zeigt auf diese konkrete, konfigurierte DataSource."

Warum das ueberhaupt lohnt

Der Witz an diesem Umweg: derselbe Code laeuft unveraendert in Test- und Produktivumgebung, obwohl beide Umgebungen auf physisch unterschiedliche Datenbanken zeigen. Nur der Telefonbucheintrag selbst (die Serverkonfiguration) unterscheidet sich - nicht eine einzige Zeile Java-Code. Das ist derselbe Grundgedanke wie eine .env-Datei oder Umgebungsvariablen in moderneren Systemen, nur als eingebauter Java-EE-Standardmechanismus, schon lange bevor Container/Kubernetes/12-Factor-Apps diese Idee popularisiert haben.

Was konkret passiert bei einem Lookup

InitialContext context = new InitialContext();
Object result = context.lookup("jdbc/novaris/policyDS");
DataSource dataSource = (DataSource) result;

Schritt fuer Schritt:

  1. new InitialContext() verbindet sich mit dem "Telefonbuch" des Anwendungsservers, in dem der aktuelle Code gerade laeuft (implizit - der Server stellt seine eigene JNDI- Implementierung automatisch bereit, kein zusaetzliches Setup im Code noetig).
  2. context.lookup("jdbc/novaris/policyDS") fragt: "was ist unter diesem Namen eingetragen?"
  3. Der Server schaut in seiner eigenen Konfiguration nach (bei Liberty: server.xml, siehe unten) und liefert das dort konfigurierte Objekt zurueck.
  4. Das Ergebnis kommt als generisches Object zurueck (JNDI kennt den konkreten Typ nicht) - deshalb der Cast auf DataSource.

In diesem Projekt siehst du diesen Ablauf konkret in JndiLookupHelper.lookup(...) (novaris-common-legacy):

InitialContext context = new InitialContext();
Object bound = context.lookup(jndiName);
if (!expectedType.isInstance(bound)) { ... }
return (T) bound;

Der bevorzugte Weg: Dependency Injection statt manuellem Lookup

Ein manueller Lookup wie oben ist in EJB-/Java-EE-Code die Ausnahme, nicht die Regel. Der Normalfall ist Dependency Injection - der Container macht denselben Lookup fuer dich, bevor deine Bean ueberhaupt benutzt wird:

@Resource(lookup = "jdbc/novaris/policyDS")
private DataSource policyDataSource;

Das ist exakt derselbe JNDI-Lookup, nur macht ihn der Container automatisch beim Erzeugen der Bean-Instanz, und er kann Fehler (Name existiert nicht) schon beim Deployment melden, statt erst beim ersten tatsaechlichen Aufruf zur Laufzeit. Siehe z.B. AuditInterceptor:

@Resource(lookup = JndiNames.ORACLE_POLICY_DATASOURCE)
private DataSource policyDataSource;

Merksatz: @Resource/@EJB/@PersistenceContext sind nichts anderes als "mach fuer mich einen JNDI-Lookup und reich mir das Ergebnis". Wenn du das einmal verinnerlicht hast, ist Dependency Injection in Java EE keine Magie mehr, sondern nur eine Abkuerzung fuer etwas, das du auch selbst schreiben koenntest (und in JndiLookupHelper teilweise auch tust).

Wo der Name tatsaechlich "eingetragen" wird

Der JNDI-Name im Code (jdbc/novaris/policyDS) ist nur die eine Haelfte. Die andere Haelfte - WAS unter diesem Namen tatsaechlich haengt - ist reine Serverkonfiguration, niemals Java-Code. In diesem Projekt: novaris-policy-core/src/main/liberty/config/server.xml:

<dataSource jndiName="jdbc/novaris/policyDS" id="policyDS">
    <jdbcDriver libraryRef="oracleJdbcLib"/>
    <properties.oracle URL="${env.NOVARIS_ORACLE_JDBC_URL}" .../>
</dataSource>

Diese Zeile ist der eigentliche "Telefonbucheintrag" - sie sagt dem Server: "wenn jemand nach jdbc/novaris/policyDS fragt, gib ihm eine DataSource, die mit diesen konkreten Verbindungsdaten konfiguriert ist." Auf einer echten WebSphere-traditional-Installation waere die aequivalente Stelle nicht eine Datei, sondern ein Eintrag in der Administrationskonsole (oder ein wsadmin-Skript) - inhaltlich dieselbe Idee, andere Werkzeuge.

Das ist der Kern, den viele beim Erlernen von JNDI verpassen: der JNDI-Name im Java-Code und die Ressourcen-Definition auf dem Server sind zwei getrennte Dinge, die nur durch den gemeinsamen String (jdbc/novaris/policyDS) miteinander verbunden sind. Es gibt keine Typpruefung, keine Autovervollstaendigung, keine Compile-Zeit-Garantie zwischen den beiden - ein Tippfehler auf einer der beiden Seiten fuehrt zu einem erst zur Laufzeit sichtbaren Fehler. Genau deshalb existiert JndiNames als zentrale Konstanten-Klasse (siehe deren Javadoc) - sie macht wenigstens die Java-Code-Seite tippfehlersicher.

Die verschiedenen Namensraeume - eine Uebersicht

JNDI-Namen in Java EE fallen in mehrere Kategorien, die leicht durcheinandergehen:

Namensform Beispiel Bedeutung
Ressourcen-JNDI-Name (Server-global) jdbc/novaris/policyDS Ein vom Server verwalteter, fest konfigurierter Name fuer eine technische Ressource (Datenquelle, JMS-Ziel). WebSphere-Konvention: <typ>/<bereich>.
Portabler globaler EJB-Name (EJB 3.1+) java:global/novaris-legacy/novaris-policy-core/PolicyManagementBean!...Local Standardisiertes Schema fuer Business-Interfaces von EJBs, serverunabhaengig. Siehe JndiNames.POLICY_MANAGEMENT_LOCAL_EXAMPLE.
Komponentenbezogener Name java:comp/env/jdbc/myDS Eine lokale, komponenteneigene Umleitung auf eine globale Ressource - siehe naechster Abschnitt. In diesem Projekt nicht direkt genutzt (wir binden meist direkt an globale Namen), aber in vielen aelteren Java-EE-Projekten der Normalfall.

Exkurs: Was ist java:comp/env/... und warum gibt es das?

Das ist der Teil, der Einsteiger am meisten verwirrt. java:comp/env ("Environment Naming Context", kurz ENC) ist ein zusaetzlicher Indirektionslayer: statt im Code direkt den globalen, serverweiten Ressourcennamen zu verwenden, definiert eine Komponente (ein Servlet, eine EJB) einen eigenen, lokalen Namen in ihrem Deployment-Deskriptor (<resource-ref>-Eintrag) und bindet diesen lokalen Namen erst dort an die tatsaechliche globale Ressource.

Warum der Umweg? Portabilitaet zwischen mehreren Instanzen derselben Anwendung. Zwei Beispiele:

Dieses Projekt verzichtet bewusst auf diesen zusaetzlichen Indirektionslayer und bindet direkt an globale Ressourcennamen (@Resource(lookup = "jdbc/novaris/policyDS") statt @Resource(name = "jdbc/policyDS") mit separatem <resource-ref>-Mapping) - einfacher zu verstehen fuer den EJB-Lernstoff dieses Projekts, auch wenn grosse, mandantenfaehige Alt-Systeme haeufig die java:comp/env-Variante nutzen. docs/10-ejb-konzepte/07-jndi-und-packaging.md geht auf das portable java:global/...-Schema als weitere Variante ein.

Ein haeufiger Stolperstein: NamingException bei einem korrekt aussehenden Namen

Wenn ein JNDI-Lookup mit NameNotFoundException fehlschlaegt, obwohl der Name im Code korrekt aussieht, liegt das fast immer an einem von drei Gruenden:

  1. Der Server hat die Ressource gar nicht gebunden - der server.xml-Eintrag (oder das Admin-Console-Aequivalent) fehlt oder wurde nicht neu geladen.
  2. Falscher Namensraum-Praefix - z.B. java:comp/env/jdbc/policyDS verwendet, obwohl die Ressource nur global unter jdbc/policyDS (ohne ENC-Indirektion) gebunden ist.
  3. Falscher Scope - ein java:module/...-Name ist nur innerhalb desselben Moduls sichtbar, ein java:app/...-Name nur innerhalb derselben Anwendung; ein Zugriff aus einem anderen Modul/einer anderen Anwendung braucht zwingend den globalen java:global/...-Namen.

Zusammenfassung in einem Satz

JNDI ist ein vom Anwendungsserver gefuehrtes Telefonbuch, das einen String-Namen im Java-Code mit einem konkret konfigurierten Objekt auf dem Server verbindet - @Resource/ @EJB/@PersistenceContext sind nur bequeme, containergesteuerte Abkuerzungen fuer genau diesen Lookup, den man (wie JndiLookupHelper zeigt) auch vollstaendig von Hand nachbauen koennte.

⌂ Cockpit