JavaRush /Blog Java /Random-FR /Utiliser JNDI en Java
Анзор Кармов
Niveau 31
Санкт-Петербург

Utiliser JNDI en Java

Publié dans le groupe Random-FR
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. Utiliser JNDI en Java - 1

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 :

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 : Utiliser JNDI en Java - 2

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 :
  1. En fin de compte, nous devons obtenir un objet Java.
  2. Nous obtiendrons cet objet à partir d'un registre.
  3. Il y a un tas d'objets dans ce registre.
  4. Chaque objet de ce registre possède un nom unique.
  5. 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. »
  6. 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).
Nous avons donc une sorte de registre, de stockage d'objets ou d'arborescence JNDI. Ensuite, à l'aide d'un exemple, essayons de comprendre la signification de JNDI. Il convient de noter que JNDI est principalement utilisé dans le développement d’entreprise. Et ces applications fonctionnent au sein d’un serveur d’applications. Ce serveur peut être un serveur d'applications Java EE, ou un conteneur de servlet comme Tomcat, ou tout autre conteneur. Le registre d'objets lui-même, c'est-à-dire l'arborescence JNDI, se trouve généralement à l'intérieur de ce serveur d'applications. Ce dernier n’est pas toujours nécessaire (vous pouvez avoir un tel arbre localement), mais c’est le plus typique. JNDI Tree peut être géré par une personne spéciale (administrateur système ou spécialiste DevOps) qui « enregistrera dans le registre » les objets avec leurs noms. Lorsque notre application et l'arborescence JNDI sont colocalisées dans le même conteneur, nous pouvons facilement accéder à n'importe quel objet Java stocké dans un tel registre. De plus, le registre et notre application peuvent être situés dans différents conteneurs et même sur différentes machines physiques. JNDI vous permet même alors d'accéder aux objets Java à distance. Cas typique. L'administrateur du serveur Java EE place un objet dans le registre qui stocke les informations nécessaires à la connexion à la base de données. En conséquence, pour travailler avec la base de données, nous demanderons simplement l'objet souhaité à l'arborescence JNDI et travaillerons avec lui. C'est très confortable. La commodité réside également dans le fait que dans le développement d’une entreprise, il existe différents environnements. Il existe des serveurs de production et des serveurs de test (et il y a souvent plus d'un serveur de test). Ensuite, en plaçant un objet de connexion à la base de données sur chaque serveur au sein de JNDI et en utilisant cet objet au sein de notre application, nous n'aurons rien à changer lors du déploiement de notre application d'un serveur (test, release) à un autre. Il y aura accès à la base de données partout. L'exemple, bien sûr, est quelque peu simplifié, mais j'espère qu'il vous aidera à mieux comprendre pourquoi JNDI est nécessaire. Ensuite, nous apprendrons à connaître de plus près JNDI en Java, avec quelques éléments d'assaut.

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).
Le code de l'API JNDI est divisé en plusieurs packages :
  • javax.naming;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
Nous commencerons notre introduction à JNDI avec deux interfaces - Nom et Contexte, qui contiennent les fonctionnalités clés de JNDI.

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)
La liaison d'un objet à un nom se fait à l'aide de méthodes bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
Les deux méthodes lieront le nom nom à l'objet. 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)
Une liste complète des méthodes est disponible sur le site de documentation officiel .

Contexte initial

InitialContextest 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é InitialContextpour récupérer des objets par nom dans l'arborescence JNDI.
Il existe plusieurs façons de l'obtenir 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 InitialContextassez 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.
Commençons dans l'ordre. Fichier->Nouveau->Projet... Utiliser JNDI en Java - 3 Ensuite, sélectionnez l' élément Spring Initializr : Utiliser JNDI en Java - 4Remplissez les métadonnées sur le projet : Utiliser JNDI en Java - 5Sélectionnez ensuite les composants Spring Framework requis. Nous allons lier certains objets DataSource, nous avons donc besoin de composants pour fonctionner avec la base de données :
  • API JDBC ;
  • Base de données H2 D.
Utiliser JNDI en Java - 6Déterminons l'emplacement dans le système de fichiers : Utiliser JNDI en Java - 7Et le projet est créé. En fait, un test unitaire a été généré automatiquement pour nous, que nous utiliserons à des fins de démonstration. Vous trouverez ci-dessous la structure du projet et le test dont nous avons besoin : Utiliser JNDI en Java - 8Commençons par écrire du code dans le test contextLoads. Un petit hack de Spring, évoqué ci-dessus, est la classe 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, InitialContextune exception sera levée lors de la création d'une instance : javax.naming.NoInitialContextException. Clause de non-responsabilité. La classe SimpleNamingContextBuilderest 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 DriverManagerDataSourceau 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(...)
Et lorsque vous travaillez avec une classe, DataSourcecela 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=

Conclusion

Aujourd'hui, nous avons examiné JNDI. Nous avons appris ce que sont les services de noms et d'annuaire et que JNDI est une API Java qui vous permet d'interagir de manière uniforme avec différents services d'un programme Java. À savoir, avec l'aide de JNDI, nous pouvons enregistrer des objets dans l'arborescence JNDI sous un certain nom et recevoir ces mêmes objets par leur nom. En guise de tâche bonus, vous pouvez exécuter un exemple du fonctionnement de JNDI. Liez un autre objet au contexte, puis lisez cet objet par son nom.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION