Привет! Сегодня мы познакомимся с тобой с JNDI. Узнаем, что это такое, для чего оно нужно, как работает, как нам с ним работать. А затем напишем Spring Boot юнит тест, внутри которого будем играться с этим самым JNDI.
![Использование JNDI в Java - 1]()
![Использование JNDI в Java - 2]()
Далее выберем пункт Spring Initializr:
Заполним метаданные о проекте:
После чего выберем необходимы компоненты Spring Framework. Мы будем привязывать какие-нибудь DataSource-объекты, поэтому нам нужны компоненты для работы с БД:
Определим расположение в файловой системе:
И проект создан. На самом деле за нас автоматически был сгенерирован один юнит тест, которым мы и воспользуемся для демонстрационных целей. Ниже — структура проекта и нужный нам тест:
Приступим к написанию кода внутри теста contextLoads.
Небольшой хак от спринга, речь о котором шла выше - это класс

Введение. Службы имен и каталогов
Прежде чем погружаться в JNDI, разберемся с тем, что же такое службы имен и каталогов. Наиболее наглядным примером такой службы является файловая система на любом ПК, ноутбуке или же смартфоне. Файловая система управляет (как это ни странно) файлами. Файлы в таких системах сгруппированы в древовидную структуру. У каждого файла есть уникальное полное имя, например: C:\windows\notepad.exe. Обрати внимание: полное имя файла представляет собой путь от некоторой корневой точки (диск C) до самого файла (notepad.exe). Промежуточными узлами в такой цепи являются каталоги (каталог windows). Файлы внутри каталогов обладают атрибутами. Например "Скрытый", "Только для чтения" и др. Подробное описание такой простой вещи как файловая система поможет лучше понять определение службы имен и каталогов. Итак, служба имен и каталогов — это система, которая управляет отображением множества имен во множестве объектов. В нашей файловой системе мы взаимодействуем с именами файлов, за которыми скрываются объекты — сами файлы в различных форматах. В службе имен и каталогов именованные объекты собраны в древовидную структуру. А объекты каталога обладают атрибутами. Еще одним примером службы имен и каталогов является DNS (англ. — Domain Name System, "система доменных имён"). Данная система управляет соответствием между понятными человеку доменными именами (например, https://javarush.com/) и понятными компьютеру IP-адресами (например, 18.196.51.113). Помимо DNS и файловых систем, есть еще уйма других служб, таких как:- Lightweight Directory Access Protocol (LDAP);
- сервис именования CORBA;
- Network Information Service (NIS);
- И прочие.
JNDI
JNDI, или же Java Naming and Directory Interface, представляет собой Java API для доступа к службам имен и каталогов. JNDI — это API, которое предоставляет единообразный механизм взаимодействия Java-программы с различными службами имен и каталогов. “Под капотом” интеграция между JNDI и любой конкретной службой осуществляется с помощью интерфейса поставщика услуг (Service Provider Interface, SPI). SPI позволяет прозрачно подключать различные службы именования и каталогов, что позволяет Java-приложению использовать JNDI API для доступа к подключенным службам. Рисунок ниже иллюстрирует архитектуру JNDI:
Источник: Oracle Java Tutorials
JNDI. Смысл простыми словами
Главный вопрос: зачем нужен JNDI? JNDI нужен для того, чтобы мы могли из Java-кода получить Java-объект из некоторой "Регистратуры" объектов по имени объекта, привязанного к этому объекту. Разобьем утверждение выше на тезисы, дабы обилие повторяющихся слов не сбило нас с толку:- В конечном итоге нам нужно получить Java-объект.
- Мы получим этот объект из некоторой регистратуры.
- В этой регистратуре есть куча объектов.
- Каждый объект в этой регистратуре обладает уникальным именем.
- Чтобы получить некоторый объект из регистратуры, мы должны в своем запросе передать имя. Как бы сказать: "Дайте мне, пожалуйста, то, что у вас лежит под таким то именем".
- Мы можем не только считывать объекты по их имени из регистратуры, но и сохранять в данной регистратуре объекты под определенными именами (как-то ведь они туда попадают).
JNDI API
JNDI поставляется внутри платформы Java SE. Для использования JNDI необходимо импортировать JNDI классы, а также один или более поставщиков услуг для доступа к службам именования и каталогов. JDK включает в себя поставщиков услуг к следующим службам:- 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.
Интерфейс Name
С помощью интерфейса Name можно управлять именами компонентов, а также синтаксисом имен в JNDI. В JNDI все операции с именами и каталогами выполняются относительно контекста. Абсолютных корней нет. Поэтому JNDI определяет InitialContext, который обеспечивает отправную точку для именования и операций с каталогами. После получения доступа к начальному контексту, его можно использовать для поиска объектов и других контекстов.
Name objectName = new CompositeName("java:comp/env/jdbc");
В коде выше мы определили некоторое имя, под которым находится некоторый объект (возможно, и не находится, но мы рассчитываем на это). Наша конечная цель — получить ссылку на этот объект и использовать её в нашей программе.
Итак, имя состоит из нескольких частей (или токенов), разделенных слэшем. Такие токены называют контекстами (context). Самый первый — просто context, все последующие — sub-context (далее по тексту — подконтекст). Контексты проще понимать, если рассматривать их как аналогию каталогов или директорий, или просто обычных папок. Корневой контекст — корневая папка. Подконтекст — вложенная папка.
Мы можем увидеть все составные части (контекст и подконтексты) данного имени, выполнив следующий код:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
Вывод будет следующим:
java:comp
env
jdbc
Вывод демонстрирует, что токены в имени отделяются друг от друга слэшем (впрочем, мы это упоминали).
Каждый токен имени имеет свой индекс. Индексация токенов начинается с 0. Нулевым индексом обладает корневой контекст, следующий контекст имеет индекс 1, следующий 2, и т.д.
Мы можем получить имя подконтекста по его индексу:
System.out.println(objectName.get(1)); // -> env
Можем также добавлять дополнительные токены (либо в конец, либо в определенное место по индексу):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
С полным перечнем методов можно ознакомиться официальной в документации.
Интерфейс Context
Данный интерфейс содержит набор констант для инициализации контекста, а также набор методов для создания и удаления контекстов, привязки объектов к имени, а также для поиска и получения объектов. Рассмотрим некоторые операции, которые выполняются с помощьюа данного интерфейса. Наиболее частое действие — поиск объекта по имени. Осуществляется с помощью методов:Object lookup(String name)
Object lookup(Name name)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
Object
Обратная операция привязке — отвязка объекта от имени, осуществляется с помощью методов unbind
:
void unbind(Name name)
void unbind(String name)
InitialContext
InitialContext
— это класс, который представляет из себя корневой элемент JNDI tree и реализует интерфейс Context
. Искать объекты по имени внутри JNDI tree нужно относительно некоторого узла. Таким узлом может служить корневой узел дерева — InitialContext
.
Типичным сценарием использования JNDI является:
- Получить
InitialContext
. - Использовать
InitialContext
для извлечения объектов по имени из JNDI tree.
InitialContext
бывает несколько. Все зависит от окружения, в котором находится Java-программа. К примеру, если Java-программа и JNDI tree запущены внутри одного и того же application сервера, получить InitialContext
довольно просто:
InitialContext context = new InitialContext();
Если же это не так, получить контекст становится немного сложнее. Порой бывает необходимо передать список пропертей окружения для инициализации контекста:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
Пример выше демонстрирует один из возможных способов инициализации контекста и иной смысловой нагрузки в себе не несет. Детально погружаться в код не нужно.
Пример использования JNDI внутри SpringBoot unit теста
Выше мы говорили о том, что для взаимодействия JNDI со службой имен и каталогов необходимо иметь под рукой SPI (Service Provider Interface), с помощью которого будет осуществляться интеграция между Джавой и службой имен. Стандартная JDK поставляется с несколькими различными SPI (выше мы их перечисляли), каждый из которых не вызывает большого интереса для демонстрационных целей. Поднять JNDI и Java приложение внутри какого-нибудь контейнера в какой-то мере интересно. Однако автор этой статьи — человек ленивый, поэтому для демонстрации работы JNDI избрал путь наименьшего сопротивления: запустить JNDI внутри юнит-теста SpringBoot приложения и получить доступ к контексту JNDI с помощью небольшого хака от Spring Framework. Итак, наш план:- Напишем пустой Spring Boot проект.
- Внутри этого проекта создадим юнит-тест.
- Внутри теста продемонстрируем работу с JNDI:
- получим доступ к контексту;
- привяжем (bind) некоторый объект под некоторым именем в JNDI;
- получим объект по его имени (lookup);
- проверим, что объект не null.



- JDBC API;
- H2 DDatabase.



SimpleNamingContextBuilder
. Данный класс предназначен для того, чтобы легко поднимать JNDI внутри юнит-тестов или же stand-alone приложений.
Напишем код для получения контекста:
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
Первые две строки кода позволят нам в дальнейшем простым образом инициализировать контекст JNDI. Без них при создании экземпляра InitialContext
будет выброшено исключение: javax.naming.NoInitialContextException
.
Дисклеймер. Класс SimpleNamingContextBuilder
является Deprecated классом. И данный пример призван показать, как можно поработать с JNDI. Это не лучшие практики по использованию JNDI внутри юнит-тестов. Это можно сказать костыль для построения контекста и демонстрации привязки и получения объектов из JNDI.
Получив контест, мы можем извлекать из него объекты или же искать объекты в контексте. Пока что в JNDI объектов нет, поэтому логично будет положить туда что-нибудь. Например, DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
В данной строке мы привязали объект класса DriverManagerDataSource
к имени java:comp/env/jdbc/datasource
.
Далее мы можем получить объект из контекста по имени. Нам ничего другого не остается, кроме как получить объект, который мы положили только что, потому что других объектов в контексте нет =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Теперь проверим, что наш DataSource имеет коннекшн (коннекшн, connection или соединение — это Java-класс, который предназначен для работы с базой данных):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Если мы все сделали правильно, вывод будет примерно таким:
conn1: url=jdbc:h2:mem:mydb user=
Стоит сказать, что некоторые строки кода могут бросить исключения.
Следующие строки бросают javax.naming.NamingException
:
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
может быть брошено java.sql.SQLException
.
В связи с этим необходимо выполнять код внутри блока try-catch
, либо указывать в сигнатуре юнит теста, что он может выбросить исключения.
Приведем полный код тестового класса:
@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();
}
}
}
После запуска теста можно наблюдать следующие логи:
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=
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ