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?
Stell dir vor, dein Code braucht eine Datenbankverbindung. Zwei Moeglichkeiten:
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."
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.
InitialContext context = new InitialContext();
Object result = context.lookup("jdbc/novaris/policyDS");
DataSource dataSource = (DataSource) result;
Schritt fuer Schritt:
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).context.lookup("jdbc/novaris/policyDS") fragt: "was ist unter diesem Namen
eingetragen?"server.xml, siehe
unten) und liefert das dort konfigurierte Objekt zurueck.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;
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).
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.
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. |
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:
java:comp/env/jdbc/policyDS an eine andere globale Datenquelle, ohne dass der Code
irgendetwas davon wissen muss.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.
NamingException bei einem korrekt aussehenden NamenWenn ein JNDI-Lookup mit NameNotFoundException fehlschlaegt, obwohl der Name im Code
korrekt aussieht, liegt das fast immer an einem von drei Gruenden:
server.xml-Eintrag (oder das
Admin-Console-Aequivalent) fehlt oder wurde nicht neu geladen.java:comp/env/jdbc/policyDS verwendet, obwohl
die Ressource nur global unter jdbc/policyDS (ohne ENC-Indirektion) gebunden ist.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.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.