Olá! Hoje apresentaremos a você o JNDI. Vamos descobrir o que é, porque é necessário, como funciona, como podemos trabalhar com ele. E então escreveremos um teste de unidade Spring Boot, dentro do qual brincaremos com este JNDI.
Introdução. Serviços de nomenclatura e diretório
Antes de mergulharmos no JNDI, vamos entender o que são serviços de nomenclatura e diretório. O exemplo mais óbvio de tal serviço é o sistema de arquivos de qualquer PC, laptop ou smartphone. O sistema de arquivos gerencia arquivos (curiosamente). Os arquivos nesses sistemas são agrupados em uma estrutura de árvore. Cada arquivo possui um nome completo exclusivo, por exemplo: C:\windows\notepad.exe. Observação: o nome completo do arquivo é o caminho de algum ponto raiz (unidade C) até o próprio arquivo (notepad.exe). Os nós intermediários dessa cadeia são diretórios (diretório do Windows). Arquivos dentro de diretórios possuem atributos. Por exemplo, “Oculto”, “Somente leitura”, etc. Uma descrição detalhada de algo tão simples como um sistema de arquivos ajudará a entender melhor a definição de nomenclatura e serviços de diretório. Portanto, um serviço de nomes e diretórios é um sistema que gerencia o mapeamento de muitos nomes para muitos objetos. Em nosso sistema de arquivos, interagimos com nomes de arquivos que ocultam objetos – os próprios arquivos em vários formatos. No serviço de nomenclatura e diretório, os objetos nomeados são organizados em uma estrutura de árvore. E os objetos de diretório possuem atributos. Outro exemplo de serviço de nomes e diretórios é o DNS (Domain Name System). Este sistema gerencia o mapeamento entre nomes de domínio legíveis por humanos (por exemplo, https://javarush.com/) e endereços IP legíveis por máquina (por exemplo, 18.196.51.113). Além de DNS e sistemas de arquivos, existem muitos outros serviços, como:- Protocolo leve de acesso a diretórios (LDAP) ;
- Serviço de nomenclatura CORBA ;
- Serviço de Informação de Rede (NIS) ;
- E outros.
JNDI
JNDI, ou Java Naming and Directory Interface, é uma API Java para acessar serviços de nomenclatura e diretório. JNDI é uma API que fornece um mecanismo uniforme para um programa Java interagir com vários serviços de nomenclatura e diretório. Nos bastidores, a integração entre JNDI e qualquer serviço é realizada usando uma Service Provider Interface (SPI). O SPI permite que vários serviços de nomenclatura e diretório sejam conectados de forma transparente, permitindo que um aplicativo Java use a API JNDI para acessar os serviços conectados. A figura abaixo ilustra a arquitetura JNDI:Fonte: Tutoriais Oracle Java
JNDI. Significado em palavras simples
A questão principal é: por que precisamos do JNDI? JNDI é necessário para que possamos obter um objeto Java de algum “registro” de objetos do código Java pelo nome do objeto vinculado a este objeto. Vamos decompor a afirmação acima em teses para que a abundância de palavras repetidas não nos confunda:- Em última análise, precisamos obter um objeto Java.
- Obteremos este objeto de algum registro.
- Existem vários objetos neste registro.
- Cada objeto neste registro possui um nome exclusivo.
- Para obter um objeto do registro, devemos passar um nome em nossa solicitação. Como se dissesse: “Por favor, dê-me o que você tem sob tal e tal nome”.
- Podemos não apenas ler objetos pelo nome no registro, mas também salvar objetos neste registro com determinados nomes (de alguma forma, eles vão parar lá).
API JNDI
JNDI é fornecido na plataforma Java SE. Para usar JNDI, você deve importar classes JNDI, bem como um ou mais provedores de serviços para acessar serviços de nomenclatura e diretório. O JDK inclui provedores de serviços para os seguintes serviços:- Protocolo Leve de Acesso a Diretórios (LDAP);
- Arquitetura Common Object Request Broker (CORBA);
- Serviço de nomes Common Object Services (COS);
- Registro de Invocação de Método Remoto Java (RMI);
- Serviço de Nome de Domínio (DNS).
- javax.nomeação;
- javax.naming.diretório;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
Nome da interface
A interface Name permite controlar nomes de componentes, bem como a sintaxe de nomenclatura JNDI. No JNDI, todas as operações de nome e diretório são executadas em relação ao contexto. Não existem raízes absolutas. Portanto, JNDI define um InitialContext, que fornece um ponto de partida para operações de nomenclatura e diretório. Uma vez acessado o contexto inicial, ele pode ser usado para pesquisar objetos e outros contextos.Name objectName = new CompositeName("java:comp/env/jdbc");
No código acima definimos algum nome sob o qual algum objeto está localizado (pode não estar localizado, mas contamos com isso). Nosso objetivo final é obter uma referência para este objeto e utilizá-lo em nosso programa. Assim, o nome consiste em várias partes (ou tokens), separadas por uma barra. Esses tokens são chamados de contextos. O primeiro é simplesmente contexto, todos os subsequentes são subcontexto (doravante denominado subcontexto). Os contextos são mais fáceis de entender se você pensar neles como análogos a diretórios ou diretórios, ou apenas como pastas normais. O contexto raiz é a pasta raiz. Subcontexto é uma subpasta. Podemos ver todos os componentes (contexto e subcontextos) de um determinado nome executando o seguinte código:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
A saída será a seguinte:
java:comp
env
jdbc
A saída mostra que os tokens no nome estão separados uns dos outros por uma barra (no entanto, mencionamos isso). Cada token de nome possui seu próprio índice. A indexação do token começa em 0. O contexto raiz tem índice zero, o próximo contexto tem índice 1, o próximo 2, etc. Podemos obter o nome do subcontexto pelo seu índice:
System.out.println(objectName.get(1)); // -> env
Também podemos adicionar tokens adicionais (no final ou em um local específico do índice):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Uma lista completa de métodos pode ser encontrada na documentação oficial .
Contexto da interface
Esta interface contém um conjunto de constantes para inicializar um contexto, bem como um conjunto de métodos para criar e excluir contextos, vincular objetos a um nome e procurar e recuperar objetos. Vejamos algumas das operações executadas usando esta interface. A ação mais comum é procurar um objeto pelo nome. Isso é feito 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
A operação inversa de vinculação - desvincular um objeto de um nome, é realizada usando os métodos unbind
:
void unbind(Name name)
void unbind(String name)
Contexto Inicial
InitialContext
é uma classe que representa o elemento raiz da árvore JNDI e implementa o Context
. Você precisa procurar objetos por nome dentro da árvore JNDI relativa a um determinado nó. O nó raiz da árvore pode servir como tal nó InitialContext
. Um caso de uso típico para JNDI é:
- Pegar
InitialContext
. - Use
InitialContext
para recuperar objetos por nome da árvore JNDI.
InitialContext
. Tudo depende do ambiente em que o programa Java está localizado. Por exemplo, se um programa Java e uma árvore JNDI estiverem rodando dentro do mesmo servidor de aplicação, é InitialContext
bastante simples obter:
InitialContext context = new InitialContext();
Se não for esse o caso, obter contexto torna-se um pouco mais difícil. Às vezes é necessário passar uma lista de propriedades do ambiente para inicializar o contexto:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
O exemplo acima demonstra uma das formas possíveis de inicializar um contexto e não carrega nenhuma outra carga semântica. Não há necessidade de mergulhar detalhadamente no código.
Um exemplo de uso de JNDI dentro de um teste de unidade SpringBoot
Acima, dissemos que para que o JNDI interaja com o serviço de nomenclatura e diretório é necessário ter em mãos o SPI (Service Provider Interface), com o qual será realizada a integração entre o Java e o serviço de nomenclatura. O JDK padrão vem com vários SPIs diferentes (listamos acima), cada um dos quais é de pouco interesse para fins de demonstração. Criar uma aplicação JNDI e Java dentro de um contêiner é um tanto interessante. Porém, o autor deste artigo é uma pessoa preguiçosa, então para demonstrar como o JNDI funciona, ele escolheu o caminho de menor resistência: executar o JNDI dentro de um teste unitário da aplicação SpringBoot e acessar o contexto JNDI usando um pequeno hack do Spring Framework. Então, nosso plano:- Vamos escrever um projeto Spring Boot vazio.
- Vamos criar um teste unitário dentro deste projeto.
- Dentro do teste demonstraremos como trabalhar com JNDI:
- ter acesso ao contexto;
- vincular (vincular) algum objeto sob algum nome em JNDI;
- obtenha o objeto pelo seu nome (pesquisa);
- Vamos verificar se o objeto não é nulo.
- APIJDBC;
- H2 DBanco de dados.
SimpleNamingContextBuilder
. Esta classe foi projetada para aumentar facilmente o JNDI dentro de testes de unidade ou aplicativos independentes. Vamos escrever o código para obter o contexto:
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
As duas primeiras linhas de código nos permitirão inicializar facilmente o contexto JNDI posteriormente. Sem eles, InitialContext
uma exceção será lançada ao criar uma instância: javax.naming.NoInitialContextException
. Isenção de responsabilidade. A classe SimpleNamingContextBuilder
é uma classe obsoleta. E este exemplo pretende mostrar como você pode trabalhar com JNDI. Estas não são práticas recomendadas para usar JNDI dentro de testes de unidade. Pode-se dizer que isso é uma muleta para construir um contexto e demonstrar a ligação e recuperação de objetos do JNDI. Tendo recebido um contexto, podemos extrair objetos dele ou procurar objetos no contexto. Ainda não há objetos no JNDI, então seria lógico colocar algo lá. Por exemplo, DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
Nesta linha, vinculamos o objeto de classe DriverManagerDataSource
ao nome java:comp/env/jdbc/datasource
. A seguir, podemos obter o objeto do contexto por nome. Não temos escolha a não ser pegar o objeto que acabamos de colocar, pois não há outros objetos no contexto =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Agora vamos verificar se nosso DataSource possui uma conexão (conexão, conexão ou conexão é uma classe Java projetada para funcionar com um banco de dados):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Se fizermos tudo corretamente, a saída será mais ou menos assim:
conn1: url=jdbc:h2:mem:mydb user=
Vale dizer que algumas linhas de código podem gerar exceções. As seguintes linhas são lançadas javax.naming.NamingException
:
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
ela pode ser lançada java.sql.SQLException
. Nesse sentido, é necessário executar o código dentro de um bloco try-catch
, ou indicar na assinatura da unidade de teste que ela pode lançar exceções. Aqui está o código completo da classe de teste:
@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();
}
}
}
Depois de executar o teste, você poderá ver os seguintes 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=
GO TO FULL VERSION