¡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.
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:- Protocolo ligero de acceso a directorios (LDAP) ;
- servicio de nombres CORBA ;
- Servicio de información de red (NIS) ;
- Y otros.
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: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:- En última instancia, necesitamos obtener un objeto Java.
- Obtendremos este objeto de algún registro.
- Hay un montón de objetos en este registro.
- Cada objeto de este registro tiene un nombre único.
- 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”.
- 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í).
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).
- javax.nombramiento;
- directorio.de.nombres.javax;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
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)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
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)
Contexto inicial
InitialContext
es 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
InitialContext
para recuperar objetos por nombre del árbol JNDI.
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 InitialContext
bastante 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.
- API JDBC;
- Base de datos H2D.
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, InitialContext
se generará una excepción al crear una instancia: javax.naming.NoInitialContextException
. Descargo de responsabilidad. La clase SimpleNamingContextBuilder
es 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 DriverManagerDataSource
al 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(...)
DataSource
se 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=
GO TO FULL VERSION