Bonjour! Aujourd'hui, nous allons vous présenter JNDI. Découvrons ce que c'est, pourquoi c'est nécessaire, comment cela fonctionne, comment nous pouvons l'utiliser. Et puis nous écrirons un test unitaire Spring Boot, dans lequel nous jouerons avec ce même JNDI.
Introduction. Services de noms et d'annuaire
Avant de plonger dans JNDI, comprenons ce que sont les services de noms et d'annuaire. L’exemple le plus évident d’un tel service est le système de fichiers de n’importe quel PC, ordinateur portable ou smartphone. Le système de fichiers gère (assez curieusement) les fichiers. Les fichiers de ces systèmes sont regroupés dans une structure arborescente. Chaque fichier possède un nom complet unique, par exemple : C:\windows\notepad.exe. Remarque : le nom complet du fichier est le chemin allant d'un point racine (lecteur C) au fichier lui-même (notepad.exe). Les nœuds intermédiaires d'une telle chaîne sont des répertoires (répertoire Windows). Les fichiers dans les répertoires ont des attributs. Par exemple, « Caché », « Lecture seule », etc. Une description détaillée d'une chose aussi simple qu'un système de fichiers aidera à mieux comprendre la définition des services de nommage et d'annuaire. Ainsi, un service de noms et d'annuaire est un système qui gère le mappage de nombreux noms sur de nombreux objets. Dans notre système de fichiers, nous interagissons avec des noms de fichiers qui cachent des objets : les fichiers eux-mêmes dans différents formats. Dans le service de noms et d'annuaire, les objets nommés sont organisés dans une arborescence. Et les objets répertoire ont des attributs. Un autre exemple de service de noms et d'annuaire est le DNS (Domain Name System). Ce système gère le mappage entre les noms de domaine lisibles par l'homme (par exemple, https://javarush.com/) et les adresses IP lisibles par machine (par exemple, 18.196.51.113). Outre le DNS et les systèmes de fichiers, il existe de nombreux autres services, tels que :- Protocole d'accès léger à l'annuaire (LDAP) ;
- Service de noms CORBA ;
- Service d'informations sur les réseaux (NIS) ;
- Et d'autres.
JNDI
JNDI, ou Java Naming and Directory Interface, est une API Java permettant d'accéder aux services de noms et d'annuaire. JNDI est une API qui fournit un mécanisme uniforme permettant à un programme Java d'interagir avec divers services de noms et d'annuaire. Sous le capot, l'intégration entre JNDI et n'importe quel service donné est réalisée à l'aide d'une interface de fournisseur de services (SPI). SPI permet à divers services de noms et d'annuaire d'être connectés de manière transparente, permettant à une application Java d'utiliser l'API JNDI pour accéder aux services connectés. La figure ci-dessous illustre l'architecture JNDI :Source : Tutoriels Oracle Java
JNDI. Signification en mots simples
La question principale est : pourquoi avons-nous besoin de JNDI ? JNDI est nécessaire pour que nous puissions obtenir un objet Java à partir d'un « enregistrement » d'objets à partir du code Java par le nom de l'objet lié à cet objet. Décomposons l'énoncé ci-dessus en thèses afin que l'abondance de mots répétés ne nous confonde pas :- En fin de compte, nous devons obtenir un objet Java.
- Nous obtiendrons cet objet à partir d'un registre.
- Il y a un tas d'objets dans ce registre.
- Chaque objet de ce registre possède un nom unique.
- Pour récupérer un objet du registre, nous devons transmettre un nom dans notre requête. Comme pour dire : « S’il vous plaît, donnez-moi ce que vous avez sous tel ou tel nom. »
- Nous pouvons non seulement lire les objets par leur nom dans le registre, mais également enregistrer les objets dans ce registre sous certains noms (ils y finissent d'une manière ou d'une autre).
API JNDI
JNDI est fourni au sein de la plateforme Java SE. Pour utiliser JNDI, vous devez importer des classes JNDI, ainsi qu'un ou plusieurs fournisseurs de services pour accéder aux services de noms et d'annuaire. Le JDK comprend des fournisseurs de services pour les services suivants :- Protocole d'accès léger à l'annuaire (LDAP) ;
- Architecture de courtier de requêtes d'objets communs (CORBA) ;
- Service de noms Common Object Services (COS);
- Registre d'invocation de méthode à distance Java (RMI) ;
- Service de noms de domaine (DNS).
- javax.naming;
- javax.naming.directory;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
Nom de l'interface
L'interface Nom vous permet de contrôler les noms des composants ainsi que la syntaxe de dénomination JNDI. Dans JNDI, toutes les opérations de nom et de répertoire sont effectuées par rapport au contexte. Il n’y a pas de racines absolues. Par conséquent, JNDI définit un InitialContext, qui fournit un point de départ pour les opérations de dénomination et de répertoire. Une fois le contexte initial accédé, il peut être utilisé pour rechercher des objets et d'autres contextes.Name objectName = new CompositeName("java:comp/env/jdbc");
Dans le code ci-dessus, nous avons défini un nom sous lequel se trouve un objet (il n'est peut-être pas localisé, mais nous comptons sur lui). Notre objectif final est d'obtenir une référence à cet objet et de l'utiliser dans notre programme. Ainsi, le nom se compose de plusieurs parties (ou jetons), séparées par une barre oblique. Ces jetons sont appelés contextes. Le tout premier est simplement un contexte, tous les suivants sont un sous-contexte (ci-après dénommé sous-contexte). Les contextes sont plus faciles à comprendre si vous les considérez comme analogues à des répertoires ou à des répertoires, ou simplement à des dossiers normaux. Le contexte racine est le dossier racine. Le sous-contexte est un sous-dossier. Nous pouvons voir tous les composants (contexte et sous-contextes) d'un nom donné en exécutant le code suivant :
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
Le résultat sera le suivant :
java:comp
env
jdbc
Le résultat montre que les jetons du nom sont séparés les uns des autres par une barre oblique (cependant, nous l'avons mentionné). Chaque jeton de nom possède son propre index. L'indexation des jetons commence à 0. Le contexte racine a l'index zéro, le contexte suivant a l'index 1, le suivant 2, etc. Nous pouvons obtenir le nom du sous-contexte par son index :
System.out.println(objectName.get(1)); // -> env
Nous pouvons également ajouter des jetons supplémentaires (soit à la fin, soit à un endroit spécifique de l'index) :
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Une liste complète des méthodes peut être trouvée dans la documentation officielle .
Contexte de l'interface
Cette interface contient un ensemble de constantes pour initialiser un contexte, ainsi qu'un ensemble de méthodes pour créer et supprimer des contextes, lier des objets à un nom et rechercher et récupérer des objets. Examinons certaines des opérations effectuées à l'aide de cette interface. L'action la plus courante consiste à rechercher un objet par son nom. Cela se fait à l'aide de méthodes :Object lookup(String name)
Object lookup(Name name)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
Object
L'opération inverse de liaison - dissocier un objet d'un nom, s'effectue à l'aide des méthodes unbind
:
void unbind(Name name)
void unbind(String name)
Contexte initial
InitialContext
est une classe qui représente l'élément racine de l'arborescence JNDI et implémente le Context
. Vous devez rechercher des objets par nom dans l'arborescence JNDI par rapport à un certain nœud. Le noeud racine de l'arbre peut servir de tel noeud InitialContext
. Un cas d’utilisation typique de JNDI est :
- Obtenir
InitialContext
. - Utilisé
InitialContext
pour récupérer des objets par nom dans l'arborescence JNDI.
InitialContext
. Tout dépend de l'environnement dans lequel se trouve le programme Java. Par exemple, si un programme Java et une arborescence JNDI s'exécutent sur le même serveur d'applications, il est InitialContext
assez simple d'obtenir :
InitialContext context = new InitialContext();
Si ce n’est pas le cas, il devient un peu plus difficile d’obtenir le contexte. Parfois il est nécessaire de passer une liste de propriétés d'environnement pour initialiser le contexte :
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
L'exemple ci-dessus montre l'une des manières possibles d'initialiser un contexte et ne comporte aucune autre charge sémantique. Il n’est pas nécessaire d’entrer dans le code en détail.
Un exemple d'utilisation de JNDI dans un test unitaire SpringBoot
Ci-dessus, nous avons dit que pour que JNDI puisse interagir avec le service de nommage et d'annuaire, il est nécessaire de disposer de SPI (Service Provider Interface), à l'aide duquel l'intégration entre Java et le service de nommage sera effectuée. Le JDK standard est livré avec plusieurs SPI différents (nous les avons listés ci-dessus), chacun présentant peu d'intérêt à des fins de démonstration. Développer une application JNDI et Java dans un conteneur est quelque peu intéressant. Cependant, l'auteur de cet article est un paresseux, donc pour démontrer le fonctionnement de JNDI, il a choisi la voie de la moindre résistance : exécutez JNDI dans un test unitaire d'application SpringBoot et accédez au contexte JNDI à l'aide d'un petit hack du Spring Framework. Alors notre plan :- Écrivons un projet Spring Boot vide.
- Créons un test unitaire dans ce projet.
- Dans le test, nous démontrerons l'utilisation de JNDI :
- accéder au contexte ;
- lier (lier) un objet sous un nom dans JNDI ;
- récupérer l'objet par son nom (recherche) ;
- Vérifions que l'objet n'est pas nul.
- API JDBC ;
- Base de données H2 D.
SimpleNamingContextBuilder
. Cette classe est conçue pour élever facilement JNDI dans des tests unitaires ou des applications autonomes. Écrivons le code pour obtenir le contexte :
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
Les deux premières lignes de code nous permettront d'initialiser facilement le contexte JNDI par la suite. Sans eux, InitialContext
une exception sera levée lors de la création d'une instance : javax.naming.NoInitialContextException
. Clause de non-responsabilité. La classe SimpleNamingContextBuilder
est une classe obsolète. Et cet exemple est destiné à montrer comment vous pouvez travailler avec JNDI. Ce ne sont pas des bonnes pratiques pour utiliser JNDI dans les tests unitaires. Cela peut être considéré comme une béquille pour construire un contexte et démontrer la liaison et la récupération d'objets depuis JNDI. Ayant reçu un contexte, nous pouvons en extraire des objets ou rechercher des objets dans le contexte. Il n'y a pas encore d'objets dans JNDI, il serait donc logique d'y mettre quelque chose. Par exemple, DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
Dans cette ligne, nous avons lié l'objet de classe DriverManagerDataSource
au nom java:comp/env/jdbc/datasource
. Ensuite, nous pouvons extraire l'objet du contexte par son nom. Nous n'avons pas d'autre choix que d'obtenir l'objet que nous venons de mettre, car il n'y a pas d'autres objets dans le contexte =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Vérifions maintenant que notre DataSource dispose d'une connexion (connexion, connexion ou connexion est une classe Java conçue pour fonctionner avec une base de données) :
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Si nous avons tout fait correctement, le résultat ressemblera à ceci :
conn1: url=jdbc:h2:mem:mydb user=
Il convient de noter que certaines lignes de code peuvent générer des exceptions. Les lignes suivantes sont lancéesjavax.naming.NamingException
:
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
cela peut être lancé java.sql.SQLException
. À cet égard, il est nécessaire d'exécuter le code à l'intérieur d'un bloc try-catch
, ou d'indiquer dans la signature de l'unité de test qu'elle peut lever des exceptions. Voici le code complet de la classe test :
@SpringBootTest
class JndiExampleApplicationTests {
@Test
void contextLoads() {
try {
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
} catch (SQLException | NamingException e) {
e.printStackTrace();
}
}
}
Après avoir exécuté le test, vous pouvez voir les journaux suivants :
o.s.m.jndi.SimpleNamingContextBuilder : Activating simple JNDI environment
o.s.mock.jndi.SimpleNamingContext : Static JNDI binding: [java:comp/env/jdbc/datasource] = [org.springframework.jdbc.datasource.DriverManagerDataSource@4925f4f5]
conn1: url=jdbc:h2:mem:mydb user=
GO TO FULL VERSION