JavaRush /Java Blog /Random-IT /Utilizzo di JNDI in Java
Анзор Кармов
Livello 31
Санкт-Петербург

Utilizzo di JNDI in Java

Pubblicato nel gruppo Random-IT
Ciao! Oggi vi presenteremo JNDI. Scopriamo cos'è, perché è necessario, come funziona, come possiamo lavorarci. E poi scriveremo uno unit test Spring Boot, all'interno del quale giocheremo proprio con questo JNDI. Utilizzo di JNDI in Java - 1

Introduzione. Servizi di denominazione e directory

Prima di immergerci in JNDI, capiamo cosa sono i servizi di denominazione e di directory. L'esempio più ovvio di tale servizio è il file system su qualsiasi PC, laptop o smartphone. Il file system gestisce (stranamente) i file. I file in tali sistemi sono raggruppati in una struttura ad albero. Ogni file ha un nome completo univoco, ad esempio: C:\windows\notepad.exe. Nota: il nome completo del file è il percorso da un punto radice (unità C) al file stesso (notepad.exe). I nodi intermedi in tale catena sono directory (directory di Windows). I file all'interno delle directory hanno attributi. Ad esempio, "Nascosto", "Sola lettura", ecc. Una descrizione dettagliata di una cosa così semplice come un file system aiuterà a comprendere meglio la definizione di denominazione e servizi di directory. Quindi, un servizio di nomi e directory è un sistema che gestisce la mappatura di molti nomi su molti oggetti. Nel nostro file system interagiamo con nomi di file che nascondono oggetti: i file stessi in vari formati. Nel servizio di denominazione e directory, gli oggetti con nome sono organizzati in una struttura ad albero. E gli oggetti della directory hanno attributi. Un altro esempio di servizio di nomi e directory è il DNS (Domain Name System). Questo sistema gestisce la mappatura tra nomi di dominio leggibili dall'uomo (ad esempio, https://javarush.com/) e indirizzi IP leggibili dalla macchina (ad esempio, 18.196.51.113). Oltre a DNS e file system, ci sono molti altri servizi, come:

JNDI

JNDI, o Java Naming and Directory Interface, è un'API Java per l'accesso ai servizi di denominazione e directory. JNDI è un'API che fornisce un meccanismo uniforme affinché un programma Java possa interagire con vari servizi di denominazione e directory. Dietro le quinte, l'integrazione tra JNDI e qualsiasi servizio viene realizzata utilizzando un'interfaccia del fornitore di servizi (SPI). SPI consente la connessione trasparente di vari servizi di denominazione e directory, consentendo a un'applicazione Java di utilizzare l'API JNDI per accedere ai servizi connessi. La figura seguente illustra l'architettura JNDI: Utilizzo di JNDI in Java - 2

Fonte: tutorial Java Oracle

JNDI. Il significato in parole semplici

La domanda principale è: perché abbiamo bisogno di JNDI? JNDI è necessario in modo che possiamo ottenere un oggetto Java da qualche "Registrazione" di oggetti dal codice Java con il nome dell'oggetto associato a questo oggetto. Suddividiamo l'affermazione di cui sopra in tesi in modo che l'abbondanza di parole ripetute non ci confonda:
  1. Alla fine dobbiamo ottenere un oggetto Java.
  2. Otterremo questo oggetto da qualche registro.
  3. Ci sono un sacco di oggetti in questo registro.
  4. Ogni oggetto in questo registro ha un nome univoco.
  5. Per ottenere un oggetto dal registro, dobbiamo passare un nome nella nostra richiesta. Come a dire: “Per favore, dammi quello che hai sotto questo o quel nome”.
  6. Non solo possiamo leggere gli oggetti in base al loro nome dal registro, ma anche salvare gli oggetti in questo registro con determinati nomi (in qualche modo finiscono lì).
Quindi, abbiamo una sorta di registro, o archiviazione di oggetti, o albero JNDI. Successivamente, utilizzando un esempio, proviamo a comprendere il significato di JNDI. Vale la pena notare che per la maggior parte JNDI viene utilizzato nello sviluppo aziendale. E tali applicazioni funzionano all'interno di alcuni server di applicazioni. Questo server potrebbe essere un Java EE Application Server o un contenitore servlet come Tomcat o qualsiasi altro contenitore. Il registro degli oggetti stesso, ovvero l'albero JNDI, si trova solitamente all'interno di questo server delle applicazioni. Quest'ultimo non è sempre necessario (puoi avere un albero del genere localmente), ma è più tipico. JNDI Tree può essere gestito da una persona speciale (amministratore di sistema o specialista DevOps) che “salverà nel registro” gli oggetti con i propri nomi. Quando la nostra applicazione e l'albero JNDI si trovano nello stesso contenitore, possiamo accedere facilmente a qualsiasi oggetto Java archiviato in tale registro. Inoltre, il registro e la nostra applicazione possono trovarsi in contenitori diversi e anche su macchine fisiche diverse. JNDI anche in questo caso consente di accedere agli oggetti Java in remoto. Caso tipico. L'amministratore del server Java EE inserisce un oggetto nel registro che memorizza le informazioni necessarie per la connessione al database. Di conseguenza, per lavorare con il database, richiederemo semplicemente l'oggetto richiesto dall'albero JNDI e lavoreremo con esso. È molto comodo La comodità sta anche nel fatto che nello sviluppo aziendale esistono ambienti diversi. Esistono server di produzione e server di test (e spesso ci sono più di 1 server di test). Quindi, posizionando un oggetto per la connessione al database su ciascun server all'interno di JNDI e utilizzando questo oggetto all'interno della nostra applicazione, non dovremo modificare nulla quando distribuiamo la nostra applicazione da un server (test, rilascio) a un altro. Ci sarà accesso al database ovunque. L'esempio, ovviamente, è un po' semplificato, ma spero che ti aiuti a capire meglio perché è necessario JNDI. Successivamente conosceremo più da vicino JNDI in Java, con alcuni elementi di assalto.

API JNDI

JNDI viene fornito all'interno della piattaforma Java SE. Per utilizzare JNDI, è necessario importare le classi JNDI, nonché uno o più fornitori di servizi per accedere ai servizi di denominazione e directory. Il JDK comprende fornitori di servizi per i seguenti servizi:
  • Protocollo di accesso alle directory leggero (LDAP);
  • Architettura Common Object Request Broker (CORBA);
  • Servizio nomi Common Object Services (COS);
  • Registro RMI (Remote Method Invocation) Java;
  • Servizio dei nomi di dominio (DNS).
Il codice API JNDI è suddiviso in diversi pacchetti:
  • javax.nome;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.evento;
  • javax.naming.spi.
Inizieremo la nostra introduzione a JNDI con due interfacce: Nome e Contesto, che contengono le funzionalità JNDI chiave

Nome dell'interfaccia

L'interfaccia Nome consente di controllare i nomi dei componenti e la sintassi dei nomi JNDI. In JNDI, tutte le operazioni sui nomi e sulle directory vengono eseguite in relazione al contesto. Non esistono radici assolute. Pertanto, JNDI definisce un InitialContext, che fornisce un punto di partenza per le operazioni di denominazione e di directory. Una volta effettuato l'accesso al contesto iniziale, è possibile utilizzarlo per cercare oggetti e altri contesti.
Name objectName = new CompositeName("java:comp/env/jdbc");
Nel codice sopra, abbiamo definito un nome con il quale si trova un oggetto (potrebbe non essere localizzato, ma ci contiamo). Il nostro obiettivo finale è ottenere un riferimento a questo oggetto e utilizzarlo nel nostro programma. Quindi, il nome è composto da più parti (o token), separate da una barra. Tali token sono chiamati contesti. Il primo è semplicemente contesto, tutti i successivi sono sottocontesto (di seguito denominato sottocontesto). I contesti sono più facili da capire se li consideri analoghi a directory o directory o semplicemente a cartelle normali. Il contesto root è la cartella root. Il sottocontesto è una sottocartella. Possiamo vedere tutti i componenti (contesto e sottocontesti) di un dato nome eseguendo il seguente codice:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}
L'output sarà il seguente:

java:comp
env
jdbc
L'output mostra che i token nel nome sono separati l'uno dall'altro da una barra (tuttavia, ne abbiamo parlato). Ogni token del nome ha il proprio indice. L'indicizzazione dei token inizia da 0. Il contesto root ha indice zero, il contesto successivo ha indice 1, il successivo 2, ecc. Possiamo ottenere il nome del sottocontesto dal suo indice:
System.out.println(objectName.get(1)); // -> env
Possiamo anche aggiungere ulteriori token (alla fine o in una posizione specifica nell'indice):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Un elenco completo dei metodi è reperibile nella documentazione ufficiale .

Contesto dell'interfaccia

Questa interfaccia contiene un insieme di costanti per inizializzare un contesto, nonché un insieme di metodi per creare ed eliminare contesti, associare oggetti a un nome e cercare e recuperare oggetti. Diamo un'occhiata ad alcune delle operazioni eseguite utilizzando questa interfaccia. L'azione più comune è cercare un oggetto per nome. Questo viene fatto utilizzando i metodi:
  • Object lookup(String name)
  • Object lookup(Name name)
L'associazione di un oggetto a un nome viene eseguita utilizzando i metodi bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
Entrambi i metodi legheranno il nome nome all'oggetto. Object L'operazione inversa di associazione - svincolo di un oggetto da un nome, viene eseguita utilizzando i metodi unbind:
  • void unbind(Name name)
  • void unbind(String name)
Un elenco completo dei metodi è disponibile sul sito web della documentazione ufficiale .

Contestoiniziale

InitialContextè una classe che rappresenta l'elemento root dell'albero JNDI e implementa Context. È necessario cercare gli oggetti per nome all'interno dell'albero JNDI relativo a un determinato nodo. Il nodo radice dell'albero può fungere da tale nodo InitialContext. Un tipico caso d'uso per JNDI è:
  • Ottenere InitialContext.
  • Utilizzare InitialContextper recuperare oggetti in base al nome dall'albero JNDI.
Ci sono diversi modi per ottenerlo InitialContext. Tutto dipende dall'ambiente in cui si trova il programma Java. Ad esempio, se un programma Java e un albero JNDI sono in esecuzione all'interno dello stesso server delle applicazioni, è InitialContextabbastanza semplice ottenere:
InitialContext context = new InitialContext();
Se così non fosse, ottenere il contesto diventa un po’ più difficile. A volte è necessario passare un elenco di proprietà dell'ambiente per inizializzare il contesto:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);
L'esempio sopra dimostra uno dei modi possibili per inizializzare un contesto e non comporta nessun altro carico semantico. Non è necessario immergersi nel codice in dettaglio.

Un esempio di utilizzo di JNDI all'interno di uno unit test SpringBoot

Sopra abbiamo detto che affinché JNDI possa interagire con il servizio di denominazione e directory, è necessario avere a portata di mano la SPI (Service Provider Interface), con l'aiuto della quale verrà effettuata l'integrazione tra Java e il servizio di denominazione. Il JDK standard viene fornito con diversi SPI diversi (li abbiamo elencati sopra), ognuno dei quali è di scarso interesse a scopo dimostrativo. Creare un'applicazione JNDI e Java all'interno di un contenitore è piuttosto interessante. Tuttavia, l'autore di questo articolo è una persona pigra, quindi per dimostrare come funziona JNDI, ha scelto il percorso di minor resistenza: eseguire JNDI all'interno di uno unit test dell'applicazione SpringBoot e accedere al contesto JNDI utilizzando un piccolo hack dallo Spring Framework. Quindi, il nostro piano:
  • Scriviamo un progetto Spring Boot vuoto.
  • Creiamo uno unit test all'interno di questo progetto.
  • All'interno del test dimostreremo come lavorare con JNDI:
    • ottenere l'accesso al contesto;
    • associare (associare) alcuni oggetti con un nome in JNDI;
    • ottenere l'oggetto in base al nome (ricerca);
    • Controlliamo che l'oggetto non sia nullo.
Cominciamo in ordine. File->Nuovo->Progetto... Utilizzo di JNDI in Java - 3 Successivamente, seleziona l' elemento Spring Initializr : Utilizzo di JNDI in Java - 4Compila i metadati relativi al progetto: Utilizzo di JNDI in Java - 5Quindi seleziona i componenti Spring Framework richiesti. Associamo alcuni oggetti DataSource, quindi abbiamo bisogno di componenti per lavorare con il database:
  • API JDBC;
  • Database H2D.
Utilizzo di JNDI in Java - 6Determiniamo la posizione nel file system: Utilizzo di JNDI in Java - 7e il progetto viene creato. Infatti per noi è stato generato automaticamente uno unit test che utilizzeremo a scopo dimostrativo. Di seguito è riportata la struttura del progetto e il test di cui abbiamo bisogno: Utilizzo di JNDI in Java - 8Iniziamo a scrivere il codice all'interno del test contextLoads. Un piccolo trucco di Spring, discusso sopra, è la classe SimpleNamingContextBuilder. Questa classe è progettata per generare facilmente JNDI all'interno di test unitari o applicazioni autonome. Scriviamo il codice per ottenere il contesto:
final SimpleNamingContextBuilder simpleNamingContextBuilder
       = new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();

final InitialContext context = new InitialContext();
Le prime due righe di codice ci permetteranno di inizializzare facilmente il contesto JNDI in seguito. Senza di essi, InitialContextverrà generata un'eccezione durante la creazione di un'istanza: javax.naming.NoInitialContextException. Disclaimer. La classe SimpleNamingContextBuilderè una classe deprecata. E questo esempio ha lo scopo di mostrare come puoi lavorare con JNDI. Queste non sono le migliori pratiche per l'utilizzo di JNDI all'interno dei test unitari. Si può dire che questo sia un supporto per costruire un contesto e dimostrare l'associazione e il recupero di oggetti da JNDI. Dopo aver ricevuto un contesto, possiamo estrarre oggetti da esso o cercare oggetti nel contesto. Non ci sono ancora oggetti in JNDI, quindi sarebbe logico inserire qualcosa lì. Per esempio, DriverManagerDataSource:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
In questa riga abbiamo associato l'oggetto classe DriverManagerDataSourceal nome java:comp/env/jdbc/datasource. Successivamente, possiamo ottenere l'oggetto dal contesto in base al nome. Non abbiamo altra scelta che ottenere l'oggetto che abbiamo appena inserito, perché non ci sono altri oggetti nel contesto =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Ora controlliamo che il nostro DataSource abbia una connessione (connessione, connessione o connessione è una classe Java progettata per funzionare con un database):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Se abbiamo fatto tutto correttamente, l'output sarà qualcosa del genere:

conn1: url=jdbc:h2:mem:mydb user=
Vale la pena dire che alcune righe di codice potrebbero generare eccezioni. Vengono lanciate le seguenti righe javax.naming.NamingException:
  • simpleNamingContextBuilder.activate()
  • new InitialContext()
  • context.bind(...)
  • context.lookup(...)
E quando si lavora con una classe DataSourcepuò essere lanciato java.sql.SQLException. A questo proposito è necessario eseguire il codice all'interno di un blocco try-catch, oppure indicare nella firma dell'unità di test che può lanciare eccezioni. Ecco il codice completo della classe di 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();
        }
    }
}
Dopo aver eseguito il test, puoi visualizzare i seguenti log:

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=

Conclusione

Oggi abbiamo esaminato JNDI. Abbiamo appreso cosa sono i servizi di denominazione e di directory e che JNDI è un'API Java che consente di interagire in modo uniforme con diversi servizi da un programma Java. Cioè, con l'aiuto di JNDI possiamo registrare oggetti nell'albero JNDI con un certo nome e ricevere gli stessi oggetti per nome. Come attività bonus, puoi eseguire un esempio di come funziona JNDI. Associa qualche altro oggetto al contesto e poi leggi questo oggetto per nome.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION