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

Usando JNDI en Java

Publicado en el grupo Random-ES
¡Hola! Hoy les presentaremos JNDI. Averigüemos qué es, por qué es necesario, cómo funciona y cómo podemos trabajar con él. Y luego escribiremos una prueba unitaria Spring Boot, dentro de la cual jugaremos con este mismo JNDI. Usando JNDI en Java - 1

Introducción. Servicios de nombres y directorios

Antes de sumergirnos en JNDI, comprendamos qué son los servicios de directorio y nombres. El ejemplo más obvio de este tipo de servicio es el sistema de archivos de cualquier PC, portátil o teléfono inteligente. El sistema de archivos gestiona (por extraño que parezca) archivos. Los archivos en dichos sistemas se agrupan en una estructura de árbol. Cada archivo tiene un nombre completo único, por ejemplo: C:\windows\notepad.exe. Tenga en cuenta: el nombre completo del archivo es la ruta desde algún punto raíz (unidad C) hasta el archivo en sí (notepad.exe). Los nodos intermedios de dicha cadena son directorios (directorio de Windows). Los archivos dentro de los directorios tienen atributos. Por ejemplo, "Oculto", "Solo lectura", etc. Una descripción detallada de algo tan simple como un sistema de archivos ayudará a comprender mejor la definición de servicios de directorio y nombres. Entonces, un servicio de nombres y directorios es un sistema que gestiona la asignación de muchos nombres a muchos objetos. En nuestro sistema de archivos, interactuamos con nombres de archivos que ocultan objetos: los archivos mismos en varios formatos. En el servicio de directorio y nombres, los objetos con nombre se organizan en una estructura de árbol. Y los objetos de directorio tienen atributos. Otro ejemplo de servicio de nombres y directorios es DNS (Sistema de nombres de dominio). Este sistema gestiona el mapeo entre nombres de dominio legibles por humanos (por ejemplo, https://javarush.com/) y direcciones IP legibles por máquinas (por ejemplo, 18.196.51.113). Además de DNS y sistemas de archivos, existen muchos otros servicios, como por ejemplo:

JNDI

JNDI, o Java Naming and Directory Interface, es una API de Java para acceder a servicios de nombres y directorios. JNDI es una API que proporciona un mecanismo uniforme para que un programa Java interactúe con varios servicios de directorio y nombres. En el fondo, la integración entre JNDI y cualquier servicio determinado se logra mediante una interfaz de proveedor de servicios (SPI). SPI permite que varios servicios de nombres y directorios se conecten de forma transparente, lo que permite que una aplicación Java utilice la API JNDI para acceder a los servicios conectados. La siguiente figura ilustra la arquitectura JNDI: Usando JNDI en Java - 2

Fuente: Tutoriales de Oracle Java

JNDI. Significado en palabras simples

La pregunta principal es: ¿por qué necesitamos JNDI? Se necesita JNDI para que podamos obtener un objeto Java a partir de algún "Registro" de objetos del código Java por el nombre del objeto vinculado a este objeto. Dividamos la afirmación anterior en tesis para que la abundancia de palabras repetidas no nos confunda:
  1. En última instancia, necesitamos obtener un objeto Java.
  2. Obtendremos este objeto de algún registro.
  3. Hay un montón de objetos en este registro.
  4. Cada objeto de este registro tiene un nombre único.
  5. Para obtener un objeto del registro, debemos pasar un nombre en nuestra solicitud. Como si dijera: “Por favor, dame lo que tienes con tal o cual nombre”.
  6. No sólo podemos leer objetos del registro por su nombre, sino también guardar objetos en este registro con ciertos nombres (de alguna manera terminan allí).
Entonces, tenemos algún tipo de registro, almacenamiento de objetos o árbol JNDI. A continuación, utilicemos un ejemplo para intentar comprender el significado de JNDI. Vale la pena señalar que, en su mayor parte, JNDI se utiliza en el desarrollo empresarial. Y dichas aplicaciones funcionan dentro de algún servidor de aplicaciones. Este servidor podría ser algún servidor de aplicaciones Java EE, un contenedor de servlets como Tomcat o cualquier otro contenedor. El registro de objetos en sí, es decir, el árbol JNDI, normalmente se encuentra dentro de este servidor de aplicaciones. Esto último no siempre es necesario (puedes tener un árbol de este tipo localmente), pero es lo más típico. El árbol JNDI puede ser administrado por una persona especial (administrador del sistema o especialista en DevOps) que “guardará en el registro” los objetos con sus nombres. Cuando nuestra aplicación y el árbol JNDI están ubicados dentro del mismo contenedor, podemos acceder fácilmente a cualquier objeto Java almacenado en dicho registro. Además, el registro y nuestra aplicación pueden estar ubicados en diferentes contenedores e incluso en diferentes máquinas físicas. JNDI incluso entonces le permite acceder a objetos Java de forma remota. Caso típico. El administrador del servidor Java EE coloca un objeto en el registro que almacena la información necesaria para conectarse a la base de datos. En consecuencia, para trabajar con la base de datos, simplemente solicitaremos el objeto deseado del árbol JNDI y trabajaremos con él. Es muy cómodo. La conveniencia también radica en el hecho de que existen diferentes entornos en el desarrollo empresarial. Hay servidores de producción y servidores de prueba (y a menudo hay más de 1 servidor de prueba). Luego, al colocar un objeto para conectarse a la base de datos en cada servidor dentro de JNDI y usar este objeto dentro de nuestra aplicación, no tendremos que cambiar nada al implementar nuestra aplicación de un servidor (prueba, lanzamiento) a otro. Habrá acceso a la base de datos en todas partes. El ejemplo, por supuesto, está algo simplificado, pero espero que le ayude a comprender mejor por qué se necesita JNDI. A continuación, conoceremos más de cerca JNDI en Java, con algunos elementos de asalto.

API JNDI

JNDI se proporciona dentro de la plataforma Java SE. Para utilizar JNDI, debe importar clases JNDI, así como uno o más proveedores de servicios para acceder a los servicios de directorio y nombres. El JDK incluye proveedores de servicios para los siguientes servicios:
  • Protocolo ligero de acceso a directorios (LDAP);
  • Arquitectura de agente de solicitud de objetos comunes (CORBA);
  • servicio de nombres de Servicios de Objetos Comunes (COS);
  • Registro de invocación de método remoto Java (RMI);
  • Servicio de nombres de dominio (DNS).
El código API JNDI se divide en varios paquetes:
  • javax.nombramiento;
  • directorio.de.nombres.javax;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
Comenzaremos nuestra introducción a JNDI con dos interfaces: Nombre y Contexto, que contienen la funcionalidad clave de JNDI.

Nombre de la interfaz

La interfaz Nombre le permite controlar los nombres de los componentes, así como la sintaxis de nombres JNDI. En JNDI, todas las operaciones de nombres y directorios se realizan en relación con el contexto. No hay raíces absolutas. Por lo tanto, JNDI define un contexto inicial, que proporciona un punto de partida para las operaciones de nombres y directorios. Una vez que se accede al contexto inicial, se puede utilizar para buscar objetos y otros contextos.
Name objectName = new CompositeName("java:comp/env/jdbc");
En el código anterior, definimos algún nombre bajo el cual se ubica algún objeto (puede que no esté ubicado, pero contamos con ello). Nuestro objetivo final es obtener una referencia a este objeto y utilizarlo en nuestro programa. Entonces, el nombre consta de varias partes (o tokens), separadas por una barra. Estos tokens se denominan contextos. El primero es simplemente contexto, todos los siguientes son subcontexto (en lo sucesivo, subcontexto). Los contextos son más fáciles de entender si los considera análogos a directorios o directorios, o simplemente carpetas normales. El contexto raíz es la carpeta raíz. El subcontexto es una subcarpeta. Podemos ver todos los componentes (contexto y subcontextos) de un nombre determinado ejecutando el siguiente código:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}
La salida será la siguiente:

java:comp
env
jdbc
El resultado muestra que los tokens en el nombre están separados entre sí por una barra (sin embargo, mencionamos esto). Cada token de nombre tiene su propio índice. La indexación de tokens comienza en 0. El contexto raíz tiene índice cero, el siguiente contexto tiene índice 1, el siguiente 2, etc. Podemos obtener el nombre del subcontexto por su índice:
System.out.println(objectName.get(1)); // -> env
También podemos agregar tokens adicionales (ya sea al final o en una ubicación específica del índice):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Puede encontrar una lista completa de métodos en la documentación oficial .

Contexto de la interfaz

Esta interfaz contiene un conjunto de constantes para inicializar un contexto, así como un conjunto de métodos para crear y eliminar contextos, vincular objetos a un nombre y buscar y recuperar objetos. Veamos algunas de las operaciones que se realizan usando esta interfaz. La acción más común es buscar un objeto por su nombre. Esto se hace usando métodos:
  • Object lookup(String name)
  • Object lookup(Name name)
Vincular un objeto a un nombre se realiza mediante métodos bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
Ambos métodos vincularán el nombre al objeto. Object La operación inversa de vinculación (desvincular un objeto de un nombre) se lleva a cabo utilizando los métodos unbind:
  • void unbind(Name name)
  • void unbind(String name)
Una lista completa de métodos está disponible en el sitio web de documentación oficial .

Contexto inicial

InitialContextes una clase que representa el elemento raíz del árbol JNDI e implementa el archivo Context. Debe buscar objetos por nombre dentro del árbol JNDI en relación con un determinado nodo. El nodo raíz del árbol puede servir como tal nodo InitialContext. Un caso de uso típico de JNDI es:
  • Conseguir InitialContext.
  • Úselo InitialContextpara recuperar objetos por nombre del árbol JNDI.
Hay varias formas de conseguirlo InitialContext. Todo depende del entorno en el que se encuentre el programa Java. Por ejemplo, si un programa Java y un árbol JNDI se ejecutan dentro del mismo servidor de aplicaciones, es InitialContextbastante sencillo obtener:
InitialContext context = new InitialContext();
Si este no es el caso, conseguir el contexto se vuelve un poco más difícil. A veces es necesario pasar una lista de propiedades del entorno para inicializar el contexto:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);
El ejemplo anterior demuestra una de las formas posibles de inicializar un contexto y no conlleva ninguna otra carga semántica. No es necesario profundizar en el código en detalle.

Un ejemplo del uso de JNDI dentro de una prueba unitaria SpringBoot

Arriba dijimos que para que JNDI interactúe con el servicio de nombres y directorio, es necesario tener a mano SPI (Service Provider Interface), con la ayuda del cual se llevará a cabo la integración entre Java y el servicio de nombres. El JDK estándar viene con varios SPI diferentes (los enumeramos anteriormente), cada uno de los cuales es de poco interés para fines de demostración. Crear una aplicación JNDI y Java dentro de un contenedor es algo interesante. Sin embargo, el autor de este artículo es una persona vaga, por lo que para demostrar cómo funciona JNDI, eligió el camino de menor resistencia: ejecutar JNDI dentro de una prueba unitaria de aplicación SpringBoot y acceder al contexto JNDI usando un pequeño truco de Spring Framework. Entonces, nuestro plan:
  • Escribamos un proyecto Spring Boot vacío.
  • Creemos una prueba unitaria dentro de este proyecto.
  • Dentro de la prueba demostraremos cómo trabajar con JNDI:
    • obtener acceso al contexto;
    • vincular (vincular) algún objeto con algún nombre en JNDI;
    • obtener el objeto por su nombre (búsqueda);
    • Comprobemos que el objeto no es nulo.
Empecemos en orden. Archivo->Nuevo->Proyecto... Usando JNDI en Java - 3 A continuación, seleccione el elemento Spring Initializr : Usando JNDI en Java - 4Complete los metadatos sobre el proyecto: Usando JNDI en Java - 5Luego seleccione los componentes Spring Framework requeridos. Vincularemos algunos objetos DataSource, por lo que necesitamos componentes para trabajar con la base de datos:
  • API JDBC;
  • Base de datos H2D.
Usando JNDI en Java - 6Determinemos la ubicación en el sistema de archivos: Usando JNDI en Java - 7Y se crea el proyecto. De hecho, se generó automáticamente una prueba unitaria, que usaremos con fines de demostración. A continuación se muestra la estructura del proyecto y la prueba que necesitamos: Usando JNDI en Java - 8Comencemos a escribir código dentro de la prueba contextLoads. Un pequeño truco de Spring, comentado anteriormente, es la clase SimpleNamingContextBuilder. Esta clase está diseñada para generar fácilmente JNDI dentro de pruebas unitarias o aplicaciones independientes. Escribamos el código para obtener el contexto:
final SimpleNamingContextBuilder simpleNamingContextBuilder
       = new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();

final InitialContext context = new InitialContext();
Las dos primeras líneas de código nos permitirán inicializar fácilmente el contexto JNDI más adelante. Sin ellos, InitialContextse generará una excepción al crear una instancia: javax.naming.NoInitialContextException. Descargo de responsabilidad. La clase SimpleNamingContextBuilderes una clase obsoleta. Y este ejemplo pretende mostrar cómo se puede trabajar con JNDI. Estas no son mejores prácticas para utilizar pruebas unitarias internas JNDI. Se puede decir que esto es una muleta para construir un contexto y demostrar cómo vincular y recuperar objetos de JNDI. Habiendo recibido un contexto, podemos extraer objetos de él o buscar objetos en el contexto. Todavía no hay objetos en JNDI, por lo que sería lógico poner algo allí. Por ejemplo, DriverManagerDataSource:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
En esta línea, hemos vinculado el objeto de clase DriverManagerDataSourceal nombre java:comp/env/jdbc/datasource. A continuación, podemos obtener el objeto del contexto por su nombre. No nos queda más remedio que conseguir el objeto que acabamos de poner, porque no hay otros objetos en el contexto =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Ahora comprobemos que nuestro DataSource tenga conexión (conexión, enlace o conexión es una clase Java que está diseñada para trabajar con una base de datos):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Si hicimos todo correctamente, el resultado será algo como esto:

conn1: url=jdbc:h2:mem:mydb user=
Vale la pena decir que algunas líneas de código pueden generar excepciones. Se lanzan las siguientes líneas javax.naming.NamingException:
  • simpleNamingContextBuilder.activate()
  • new InitialContext()
  • context.bind(...)
  • context.lookup(...)
Y cuando se trabaja con una clase, DataSourcese puede descartar java.sql.SQLException. En este sentido, es necesario ejecutar el código dentro de un bloque try-catch, o indicar en la firma de la unidad de prueba que puede arrojar excepciones. Aquí está el código completo de la clase de prueba:
@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();
        }
    }
}
Después de ejecutar la prueba, podrá ver los siguientes registros:

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=

Conclusión

Hoy analizamos JNDI. Aprendimos qué son los servicios de directorio y nombres, y que JNDI es una API de Java que le permite interactuar de manera uniforme con diferentes servicios de un programa Java. Es decir, con la ayuda de JNDI, podemos registrar objetos en el árbol JNDI con un nombre determinado y recibir esos mismos objetos por nombre. Como tarea adicional, puede ejecutar un ejemplo de cómo funciona JNDI. Vincule algún otro objeto al contexto y luego lea este objeto por su nombre.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION