JavaRush /Java Blog /Random EN /Using JNDI in Java
Анзор Кармов
Level 31
Санкт-Петербург

Using JNDI in Java

Published in the Random EN group
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. Using JNDI in Java - 1

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:

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

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:
  1. Ultimately we need to get a Java object.
  2. We will get this object from some registry.
  3. There are a bunch of objects in this registry.
  4. Each object in this registry has a unique name.
  5. 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.”
  6. 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).
So, we have some kind of registry, or object storage, or JNDI Tree. Next, using an example, let's try to understand the meaning of JNDI. It is worth noting that for the most part JNDI is used in Enterprise development. And such applications work inside some application server. This server could be some Java EE Application Server, or a servlet container like Tomcat, or any other container. The object registry itself, that is, the JNDI Tree, is usually located inside this application server. The latter is not always necessary (you can have such a tree locally), but it is most typical. JNDI Tree can be managed by a special person (system administrator or DevOps specialist) who will “save in the registry” objects with their names. When our application and JNDI Tree are co-located inside the same container, we can easily access any Java object that is stored in such a registry. Moreover, the registry and our application can be located in different containers and even on different physical machines. JNDI even then allows you to access Java objects remotely. Typical case. The Java EE server administrator places an object in the registry that stores the necessary information for connecting to the database. Accordingly, to work with the database, we will simply request the desired object from the JNDI tree and work with it. It is very comfortable. Convenience also lies in the fact that in enterprise development there are different environments. There are production servers, and there are test servers (and often there are more than 1 test server). Then, by placing an object for connecting to the database on each server inside JNDI and using this object inside our application, we will not have to change anything when deploying our application from one server (test, release) to another. There will be access to the database everywhere. The example, of course, is somewhat simplified, but I hope it will help you better understand why JNDI is needed. Next, we will get to know JNDI in Java more closely, with some elements of assault.

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).
The JNDI API code is divided into several packages:
  • javax.naming;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
We will begin our introduction to JNDI with two interfaces - Name and Context, which contain key JNDI functionality

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)
Binding an object to a name is done using methods bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
Both methods will bind the name name to the object. 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)
A complete list of methods is available on the official documentation website .

InitialContext

InitialContextis 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 InitialContextto retrieve objects by name from the JNDI tree.
There are several ways to get it 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 InitialContextquite 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.
Let's start in order. File->New->Project... Using JNDI in Java - 3 Next, select the Spring Initializr item : Using JNDI in Java - 4Fill in the metadata about the project: Using JNDI in Java - 5Then select the required Spring Framework components. We will bind some DataSource objects, so we need components to work with the database:
  • JDBC API;
  • H2 DDatabase.
Using JNDI in Java - 6Let's determine the location in the file system: Using JNDI in Java - 7And 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: Using JNDI in Java - 8Let's start writing code inside the contextLoads test. A small hack from Spring, discussed above, is the class 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, InitialContextan exception will be thrown when creating an instance: javax.naming.NoInitialContextException. Disclaimer. The class SimpleNamingContextBuilderis 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 DriverManagerDataSourceto 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(...)
And when working with a class DataSourceit 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=

Conclusion

Today we looked at JNDI. We learned about what naming and directory services are, and that JNDI is a Java API that allows you to uniformly interact with different services from a Java program. Namely, with the help of JNDI, we can record objects in the JNDI tree under a certain name and receive these same objects by name. As a bonus task, you can run an example of how JNDI works. Bind some other object into the context, and then read this object by name.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION