Hello! Today we will introduce you to JNDI. Let's find out what it is, why it is needed, how it works, how we can work with it. And then we’ll write a Spring Boot unit test, inside of which we’ll play with this very JNDI.
Next, select the Spring Initializr item :
Fill in the metadata about the project:
Then select the required Spring Framework components. We will bind some DataSource objects, so we need components to work with the database:
Let's determine the location in the file system:
And the project is created. In fact, one unit test was automatically generated for us, which we will use for demonstration purposes. Below is the project structure and the test we need:
Let's start writing code inside the contextLoads test. A small hack from Spring, discussed above, is the class
Introduction. Naming and Directory Services
Before diving into JNDI, let's understand what naming and directory services are. The most obvious example of such a service is the file system on any PC, laptop or smartphone. The file system manages (oddly enough) files. Files in such systems are grouped in a tree structure. Each file has a unique full name, for example: C:\windows\notepad.exe. Please note: the full file name is the path from some root point (drive C) to the file itself (notepad.exe). The intermediate nodes in such a chain are directories (windows directory). Files inside directories have attributes. For example, “Hidden”, “Read-Only”, etc. A detailed description of such a simple thing as a file system will help to better understand the definition of naming and directory services. So, a name and directory service is a system that manages the mapping of many names to many objects. In our file system, we interact with file names that hide objects—the files themselves in various formats. In the naming and directory service, named objects are organized into a tree structure. And directory objects have attributes. Another example of a name and directory service is DNS (Domain Name System). This system manages the mapping between human-readable domain names (for example, https://javarush.com/) and machine-readable IP addresses (for example, 18.196.51.113). Besides DNS and file systems, there are a lot of other services, such as:- Lightweight Directory Access Protocol (LDAP) ;
- CORBA naming service ;
- Network Information Service (NIS) ;
- And others.
JNDI
JNDI, or Java Naming and Directory Interface, is a Java API for accessing naming and directory services. JNDI is an API that provides a uniform mechanism for a Java program to interact with various naming and directory services. Under the hood, integration between JNDI and any given service is accomplished using a Service Provider Interface (SPI). SPI allows various naming and directory services to be transparently connected, allowing a Java application to use the JNDI API to access the connected services. The figure below illustrates the JNDI architecture:![Using JNDI in Java - 2](https://cdn.javarush.com/images/article/bba8e179-d07d-4971-9757-01b9724c5e5a/512.webp)
Source: Oracle Java Tutorials
JNDI. Meaning in simple words
The main question is: why do we need JNDI? JNDI is needed so that we can get a Java object from some “Registration” of objects from Java code by the name of the object bound to this object. Let’s break the statement above into theses so that the abundance of repeated words does not confuse us:- Ultimately we need to get a Java object.
- We will get this object from some registry.
- There are a bunch of objects in this registry.
- Each object in this registry has a unique name.
- To get an object from the registry, we must pass a name in our request. As if to say: “Please give me what you have under such and such a name.”
- We can not only read objects by their name from the registry, but also save objects in this registry under certain names (somehow they end up there).
JNDI API
JNDI is provided within the Java SE platform. To use JNDI, you must import JNDI classes, as well as one or more service providers to access naming and directory services. The JDK includes service providers for the following services:- Lightweight Directory Access Protocol (LDAP);
- Common Object Request Broker Architecture (CORBA);
- Common Object Services (COS) name service;
- Java Remote Method Invocation (RMI) Registry;
- Domain Name Service (DNS).
- javax.naming;
- javax.naming.directory;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
Interface Name
The Name interface allows you to control component names as well as JNDI naming syntax. In JNDI, all name and directory operations are performed relative to the context. There are no absolute roots. Therefore, JNDI defines an InitialContext, which provides a starting point for naming and directory operations. Once the initial context is accessed, it can be used to search for objects and other contexts.Name objectName = new CompositeName("java:comp/env/jdbc");
In the code above, we defined some name under which some object is located (it may not be located, but we are counting on it). Our final goal is to obtain a reference to this object and use it in our program. So, the name consists of several parts (or tokens), separated by a slash. Such tokens are called contexts. The very first one is simply context, all subsequent ones are sub-context (hereinafter referred to as subcontext). Contexts are easier to understand if you think of them as analogous to directories or directories, or just regular folders. The root context is the root folder. Subcontext is a subfolder. We can see all the components (context and subcontexts) of a given name by running the following code:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
The output will be as follows:
java:comp
env
jdbc
The output shows that the tokens in the name are separated from each other by a slash (however, we mentioned this). Each name token has its own index. Token indexing starts at 0. The root context has index zero, the next context has index 1, the next 2, etc. We can get the subcontext name by its index:
System.out.println(objectName.get(1)); // -> env
We can also add additional tokens (either at the end or at a specific location in the index):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
A complete list of methods can be found in the official documentation .
Interface Context
This interface contains a set of constants for initializing a context, as well as a set of methods for creating and deleting contexts, binding objects to a name, and searching for and retrieving objects. Let's look at some of the operations that are performed using this interface. The most common action is to search for an object by name. This is done using methods:Object lookup(String name)
Object lookup(Name name)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
Object
The inverse operation of binding - unbinding an object from a name, is carried out using the methods unbind
:
void unbind(Name name)
void unbind(String name)
InitialContext
InitialContext
is a class that represents the root element of the JNDI tree and implements the Context
. You need to search for objects by name inside the JNDI tree relative to a certain node. The root node of the tree can serve as such a node InitialContext
. A typical use case for JNDI is:
- Get
InitialContext
. - Use
InitialContext
to retrieve objects by name from the JNDI tree.
InitialContext
. It all depends on the environment in which the Java program is located. For example, if a Java program and a JNDI tree are running inside the same application server, it is InitialContext
quite simple to get:
InitialContext context = new InitialContext();
If this is not the case, getting context becomes a little more difficult. Sometimes it is necessary to pass a list of environment properties to initialize the context:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
The example above demonstrates one of the possible ways to initialize a context and does not carry any other semantic load. There is no need to dive into the code in detail.
An example of using JNDI inside a SpringBoot unit test
Above, we said that in order for JNDI to interact with the naming and directory service, it is necessary to have SPI (Service Provider Interface) at hand, with the help of which integration between Java and the naming service will be carried out. The standard JDK comes with several different SPIs (we listed them above), each of which is of little interest for demonstration purposes. Raising a JNDI and Java application inside a container is somewhat interesting. However, the author of this article is a lazy person, so to demonstrate how JNDI works, he chose the path of least resistance: run JNDI inside a SpringBoot application unit test and access the JNDI context using a small hack from the Spring Framework. So, our plan:- Let's write an empty Spring Boot project.
- Let's create a unit test inside this project.
- Inside the test we will demonstrate working with JNDI:
- get access to the context;
- bind (bind) some object under some name in JNDI;
- get the object by its name (lookup);
- Let's check that the object is not null.
![Using JNDI in Java - 3](https://cdn.javarush.com/images/article/b91c7249-661a-41da-a482-b045db2f53d8/800.webp)
![Using JNDI in Java - 4](https://cdn.javarush.com/images/article/7916acb5-6f97-41b7-add8-21e74aad6f70/800.webp)
![Using JNDI in Java - 5](https://cdn.javarush.com/images/article/a1707760-212c-4084-ab25-fcab1a527da2/800.webp)
- JDBC API;
- H2 DDatabase.
![Using JNDI in Java - 6](https://cdn.javarush.com/images/article/e9d47924-d95c-432c-a988-2989a1807615/1024.webp)
![Using JNDI in Java - 7](https://cdn.javarush.com/images/article/1f9706fb-4dd4-4431-89df-11ab6eecd148/800.webp)
![Using JNDI in Java - 8](https://cdn.javarush.com/images/article/9b81a366-06b5-4259-9033-be6efde79f3f/800.webp)
SimpleNamingContextBuilder
. This class is designed to easily raise JNDI inside unit tests or stand-alone applications. Let's write the code to get the context:
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
The first two lines of code will allow us to easily initialize the JNDI context later. Without them, InitialContext
an exception will be thrown when creating an instance: javax.naming.NoInitialContextException
. Disclaimer. The class SimpleNamingContextBuilder
is a Deprecated class. And this example is intended to show how you can work with JNDI. These are not best practices for using JNDI inside unit tests. This can be said to be a crutch for building a context and demonstrating binding and retrieving objects from JNDI. Having received a context, we can extract objects from it or search for objects in the context. There are no objects in JNDI yet, so it would be logical to put something there. For example, DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
In this line, we have bound the class object DriverManagerDataSource
to the name java:comp/env/jdbc/datasource
. Next, we can get the object from the context by name. We have no choice but to get the object that we just put, because there are no other objects in the context =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Now let’s check that our DataSource has a connection (connection, connection or connection is a Java class that is designed to work with a database):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
If we did everything correctly, the output will be something like this:
conn1: url=jdbc:h2:mem:mydb user=
It is worth saying that some lines of code may throw exceptions. The following lines are thrown javax.naming.NamingException
:
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
it can be thrown java.sql.SQLException
. In this regard, it is necessary to execute the code inside a block try-catch
, or indicate in the signature of the test unit that it can throw exceptions. Here is the complete code of the test class:
@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();
}
}
}
After running the test, you can see the following logs:
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=