Gerenciamento de Dependência com JNDI

Veja neste artigo uma introdução ao gerenciamento de dependências através de JNDI (Java Naming and Directory Interface). Veremos como nomear recursos com as anotações e como pesquisar as dependências criadas. Além disso, veremos exemplos da utilização da API JNDI e alternativas para pesquisar determinados componentes.

A lógica de negócio de uma aplicação nem sempre é independente, muitas vezes nossa implementação depende de outros recursos que estão no servidor de aplicação como JDBC, JMS, Session Bean e Entity Manager.

Para gerenciar essas dependências, os componentes Java EE oferecem referências para recursos que estão definidos em meta-dados externos ou anotações. Essas referências nada mais são do que um link nomeado para os recursos que pode ser determinado dinamicamente em tempo de execução dentro do código da aplicação ou automaticamente pelo container quando a instância do componente é criada.

Toda referência tem um nome e um destino. O nome é usado pelo código da aplicação para determinar a referência dinamicamente. O servidor usa a informação de destino para encontrar o recurso que a aplicação está procurando.

Pesquisa de Dependência

A forma mais tradicional de gerenciamento de dependência em Java EE é através da pesquisa de dependência na qual o código da aplicação usa o Java Naming and Directory Interface (ou JNDI) para localizar uma referência nomeada.

A JNDI ou Java Naming and Directory Interface é uma API para acesso a serviços de diretórios. Ela permite que aplicações cliente descubram e obtenham dados ou objetos através de um nome. Assim como todas as APIs Java, ela é independente de plataforma. Suas duas funções básicas são Associar (mapear) um nome a um recurso e localizar um recurso a partir de seu nome.

Todas as anotações de recursos suportam o atributo "name" que define o nome da referência. Quando a anotação de recurso é colocada na definição da classe, este atributo é obrigatório. Caso a anotação seja colocada num atributo ou num método setter, o servidor irá gerar um nome default. Frequentemente colocamos anotações no nível da classe, e assim o nome é explicitamente especificado.

O papel do nome é prover um caminho para o cliente localizar a referência dinamicamente. Todo servidor de aplicação Java EE suporta JNDI, e cada componente tem seu próprio contexto de nomeação JNDI local que é chamado de ambiente de contexto de nomeação. O nome da referência é vinculado ao ambiente de contexto de nomeação, e quando for procurado usando a API JNDI, o servidor determina a referência e retorna o destino da referência.

Como um exemplo podemos considerar o bean de sessão abaixo. No exemplo temos uma dependência com um bean de sessão através da anotação @EJB, o bean de sessão se chama "audit", conforme é definido com o atributo "name". O atributo beanInterface apenas define a interface de negócio do bean de sessão que o cliente está interessado. No @PostConstruct o bean de sessão "audit" é localizado e armazenado no atributo "audit". As interfaces Context e InitialContext são ambas definidas pela API JNDI. O método lookup() da interface Context é o primeiro ponto para retornar objetos de um contexto JNDI. Para encontrar a referência "audit", a aplicação procura o nome "java:comp/env/audit" e faz um cast (conversão) do resultado para a interface de negócio AuditService. O prefixo "java:comp/env/" adicionado no nome da referência indica para o servidor que o ambiente de contexto de nomeação deveria ser pesquisado para encontrar a referência. Se o nome estiver incorreto, uma exceção será lançada quando a pesquisa falhar.

Listagem 1: Encontrando uma dependência EJB.

@Stateless
@EJB(name="audit", beanInterface=AuditService.class)
public class DeptServiceBean implements DeptService {
	private AuditService audit;
	@PostConstruct
	public void init() {
		try {
			Context ctx = new InitialContext();
			audit = (AuditService) ctx.lookup("java:comp/env/audit");
		} catch (NamingException e) {
			throw new EJBException(e);
		}
	}
// ...
}

Apesar da forma acima ser válida e suportada em larga escala pelos componentes Java EE, podemos notar que é um pouco complicado de utilizá-lo devido ao tratamento de exceção requerido pela JNDI. EJBs também suportam uma sintaxe alternativa usando um método lookup() da interface EJBContext. A interface EJBContext (e as subinterfaces SessionContext e MessageDrivenContext) está disponível para qualquer EJB e proveem o bean com acesso a serviços em tempo de execução, tais como serviços de timer. O mesmo exemplo acima é apresentado abaixo usando o método lookup() alternativo. A instância SessionContext neste exemplo é provido via método setter.

Listagem 2: Encontrando uma dependência usando lookup EJB.

@Stateless
@EJB(name="audit", beanInterface=AuditService.class)
public class DeptServiceBean implements DeptService {
	SessionContext context;
	AuditService audit;
	
	public void setSessionContext(SessionContext context) {
		this.context = context;
	}

	@PostConstruct
	public void init() {
		audit = (AuditService) context.lookup("audit");
	}

	// ...

}

O método lookup() do EJBContext tem duas vantagens principais sobre a API JNDI: Primeiramente é que o argumento para o método é exatamente o nome que foi especificado na referência de recurso. A segunda vantagem é que apenas exceções em tempo de execução são lançadas pelo método lookup(), as outras exceções checadas da API JNDI podem ser evitadas. O gerenciamento das exceções são feitas automaticamente, porém escondidas do cliente.

Conclusão

Neste artigo vimos que uma aplicação depende de outros componentes e que podemos utilizar a pesquisar de dependência para localizar estes componentes. Uma das formas é utilizar a API JNDI (Java Naming and Directory Interface) onde ela permite que as aplicações clientes descubram e obtenham dados ou objetos através de um nome, ou ainda podemos utilizar uma interface de suporte para localizar EJBs que por sua vez possui algumas vantagens na sua utilização ao invés da API JNDI.

Referências