RMI/JNDI - Fundamentos

Propaganda
Professor J
coluna
RMI/JNDI -
Fundamentos
Um exemplo prático do que são e de como
funcionam RMI e JNDI
Roberto Vezzoni
([email protected]): SCJP, faz
Ciência da Computação na Faesa e atua
como assessor/instrutor de TI.
A API RMI (Remote Method Invocation) é o mecanismo em Java que permite a
manipulação de objetos distribuídos. Com RMI, é possível realizar chamadas de
métodos em objetos remotos (numa outra VM, talvez no mesmo host). Como é
necessário vasculhar outra VM para a tentativa de encontrar um objeto remoto,
nós precisamos fornecer meios para que aquele objeto seja, de fato, encontrado.
É através da API JNDI (Java Naming and Directory Interface) que conseguimos
registrar e encontrar objetos remotos para que estes sejam utilizados por RMI.
O
diretor de compras de uma empresa, por conta de suas viagens
a negócios, solicita ao departamento de TI a possibilidade de
acessar e manipular informações sobre pedidos de compras e estoque
disponível através de seu smartphone.
Como já existe um sistema de gestão para pedidos de compras que
foi desenvolvido em duas camadas (cliente/servidor) e sendo este um
sistema desktop para janelas mais ricas em recursos de entrada pelo
usuário, será necessário quebrar o processo de pedido de compras em
três camadas lógicas para evitar a duplicação do código na camada de
negócios da nova aplicação.
O cenário que possuímos é de que ainda será possível realizar a manipulação dos pedidos de compras no sistema de gestão já implantado e
temos um novo sistema que permite acesso através daquele smartphone
e demais dispositivos móveis compatíveis. A arquitetura deste cenário
está demonstrada na figura 1.
O padrão de projeto Façade (ou Fachada, em português) é responsável por fornecer um acesso padronizado e unificado a um
subsistema mais complexo.
Imagine, por exemplo, o processo de cadastro de nota fiscal de
entrada por compra para comercialização. Será necessário além
de fazer entrada em estoque das mercadorias envolvidas, fazer um
lançamento de contas a pagar ao fornecedor em questão. Assim,
o programador se vê em meio a várias regras de negócio. Para resolver essa situação, pode ser fornecido ao programador um objeto
responsável por fazer o fechamento daquela nota fiscal, realizando
apenas uma chamada, em vez de implementar um código com
várias instâncias de objetos diferentes que se relacionam entre si. A
esse novo objeto, se dá o nome de Façade.
Num paralelo com tecnologias para este fim, além da RMI, temos como
exemplos os padrões COM e DCOM (Microsoft) e o padrão CORBA (Common
Object Request Broken Architeture) do OMG (Object Management Group).
Como os objetos em RMI trafegam entre diferentes VMs, é necessário o uso
22 www.mundoj.com.br
'JHVSB"SRVJUFUVSBVUJMJ[BEBQBSBFYFNQMJmDBÎÍPEFVTPEFTJTUFNBEJTUSJCVÓEPBDSFEJUFP
UFMFGPOFWFSNFMIPEBmHVSBÏVNTNBSUQIPOF
de protocolos para a comunicação entre as aplicações cliente e servidor, e o
protocolo padrão é o JRMP (Java Remote Method Protocol). Para se obter
interoperabilidade entre diferentes linguagens de programação orientadas
a objetos que seguem o padrão do OMG, o protocolo utilizado é o IIOP
(Internet Inter-ORB Protocol) no qual esse ORB (Object Request Broker) atua
fazendo o "meio de campo" entre o cliente e o servidor.
Quando é citado RMI-IIOP, significa explicitar que os objetos trafegados por
RMI estão sobre o protocolo IIOP. EJBs (Enterprise JavaBeans) utilizam RMIIIOP por "trás das cenas".
Para que tudo isso funcione, nós precisamos de arquivos auxiliares que
serão responsáveis por, de fato, realizar chamadas em métodos de objetos
remotos. Com esses arquivos disponíveis em nosso classpath, realizamos
chamadas em objetos remotos como se fossem locais, pois eles atuam como
proxies. Esses arquivos são conhecidos como skeleton e stub e ficam no
servidor e cliente, respectivamente. Mais adiante veremos como gerar tais
arquivos.
Um proxy é responsável por gerenciar o tráfego de informações
entre as aplicações cliente e servidor.
1SPGFTTPS+t3.*+/%*'VOEBNFOUPT
você tenha permissão para escrita de seu sistema de arquivos (os arquivos de código-fonte já inclusos nesta árvore serão criados por nós neste
arquivo).
Figura 2. Árvore de diretórios para nosso exemplo.
Passagem de argumentos
Quando usamos RMI devemos estar atentos ao tratamento das passagens de argumentos, pois em Java passagens de argumentos se dão
por cópia de valor. Mas o que acontece quando passamos objetos como
argumentos em RMI? A referência de um determinado objeto local
(endereço na memória heap local) pode não existir na VM remota e se
existir não significa que seja o que esperamos ser, então caímos numa
exceção na passagem de argumentos em Java: passagem de argumentos
por referência.
Tudo bem, na verdade é uma pseudopassagem por referência, pois o objeto utilizado como argumento é enviado para a outra VM. Isso acontece
por meio de serialização, ou seja, devemos fazer com que esse objeto
que deve ser trafegado implemente a interface java.io.Serializable.
Caso o objeto não deva ser enviado para que o volume de dados trafegados seja menor, basta não implementar aquela interface. Mas como
não existe “almoço grátis”, ao não implementarmos a interface java.
io.Serializable, deixamos de trafegar um objeto pela rede, porém agora
para cada mudança de estado desse objeto deverá ser realizada uma
chamada de rede.
Servidor
Para começar a programação, iremos montar um objeto que contenha uma
operação simples e, seguindo uma máxima para encapsulamento e baixo
acoplamento na orientação a objetos, vamos programar para interfaces. Não
faremos isso apenas por uma boa prática, em RMI não temos escolha, é impossível fazer chamadas diretamente ao objeto que mantém a implementação.
Devido a essa regra, precisamos construir uma interface que estenda java.rmi.
Remote, com os métodos desejados.
Salve o código da Listagem 1 em um arquivo chamado FacadeIntf.java em /
rmi_home/server/src/mj/rmiserver/intf.
Listagem 1. mj.rmiserver.intf.ServidorIntf.java.
package mj.rmiserver.intf;
public interface FacadeIntf extends java.io.Serializable, java.rmi.Remote {
String JNDI_NAME = “Server”;
Objetivo: “Hello World”
String sayHello() throws java.rmi.RemoteException;
Bom, mãos à obra!
Nossa intenção é construir uma aplicação RMI stand alone, ou seja, ela
deve executar fora de um container JEE (Java Enterprise Edition). Ela nos
mostrará como as coisas funcionam exibindo o nosso já bem conhecido
“Hello World”.
Configuração do ambiente
A primeira coisa a fazer é criar uma estrutura de diretórios em nosso
sistema de arquivos para organizar de forma adequada nossos artefatos.
Crie a árvore de diretórios conforme a figura 2 em qualquer parte que
}
Todas as operações declaradas em nossa interface remota devem lançar java.
rmi.RemoteException, pois a RMI força-nos a considerar a possibilidade de
instabilidades de rede, travamentos em máquinas remotas, etc.
Agora devemos implementar nossa interface montando nosso objeto remoto.
Para tal, precisamos estender uma classe especial: javax.rmi.PortableRemoteObject.
Salve o código da Listagem 2 em um arquivo com o nome FacadeImpl.java em
rmi_home/server/src/mj/rmiserver/impl.
23
Professor J
1SPGFTTPS+t3.*+/%*'VOEBNFOUPT
Listagem 2. mj.remiserver.impl.FacadeImpl.java.
Listagem 3. mj.rmiserver.ServerRMI.
package mj.rmiserver.impl;
package mj.rmiserver;
import mj.rmiserver.intf.FacadeIntf;
public class ServerRMI {
public class FacadeImpl extends javax.rmi.PortableRemoteObject implements
FacadeIntf {
public ServerRMI() {
try {
public FacadeImpl() throws java.rmi.RemoteException {
super();
}
/*
* cria um registro (responsável por aceitar requisições) na VM
* local na porta padrão
* para distribuição de objetos sobre RMI - 1099.
* Assim, sempre que a máquina, em que reside o nosso servidor
* em execução,
* receber solicitações na porta 1099,
* essa solicitação será encaminhada para o nosso servidor.
*/
java.rmi.registry.LocateRegistry
.createRegistry( java.rmi.registry.Registry.REGISTRY_PORT );
public String sayHello() {
return “Hello World!”;
}
}
Agora nos resta montar uma classe que seja responsável por levantar
nosso servidor (todos os artefatos deste artigo estão disponíveis no site
da revista Mundoj). Ela é mostrada na Listagem 3.
Agora devemos compilar as classes do nosso servidor. Para isso, basta compilar
o arquivo ServerRMI.java que todas as outras classes utilizadas por ele também
serão compiladas. Entre no diretório rmi_home/server/src e execute o seguinte
comando:
mj.rmiserver.impl.FacadeImpl facade = new mj.rmiserver.impl.FacadeImpl();
O utilitário rmic, que vem junto com o JDK, deve ser usado para gerar arquivos
auxiliares para cada um dos objetos registrados na VM e deve ser executado
sobre o bytecode. Entre no diretório rmi_home/server/bin e execute o seguinte comando:
/*
* amarra a instância de nosso objeto remoto a um nome
* (examinaremos esse “nome” mais adiante)
* e armazenamos isso no registro.
* A classe java.rmi.Naming é responsável por fornecedor métodos
* para armazenar e obter objetos remotos no registro.
*/
java.rmi.Naming
.bind( mj.rmiserver.intf.FacadeIntf.JNDI_NAME, facade );
rmic -classpath . mj.rmiserver.impl.FacadeImpl
synchronized (facade) {
javac -d ../bin/ -cp . mj/rmiserver/ServerRMI.java
Para complementar nosso servidor, precisamos apenas gerar um arquivo auxiliar para que a RMI saiba como lidar com nosso objeto remoto.
De posse do arquivo auxiliar FacadeImpl_Stub.class gerado no diretório rmi_
home/server/bin/impl, devemos disponibilizá-lo nos classpaths das aplicações
cliente e servidor.
try {
/*
* chamada ao método wait() herdado de java.lang.Object
* para que nosso servidor fique esperando por requisições.
* E fazemos isso dentro de um bloco sincronizado
* pois queremos nosso servidor trabalhando de forma thread-safe.
*/
System.out.println(“esperando por requisições...”);
facade.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
Assim, podemos executar nosso servidor. Entre no diretório rmi_home/server/
bin e execute o seguinte comando:
java -cp . mj.rmiserver.ServerRMI
A seguinte saída lhe será apresentada:
esperando por requisições...
(Uau, 'tá funcionando mesmo! :o)
}
Resumo
} catch (java.rmi.RemoteException re) {
re.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Esse servidor, em execução, criará um registro na VM local esperando por requisições na porta 1099. E também terá uma instância da classe mj.rmiserver.impl.
FacadeImpl.java amarrada ao nome “Server” (ainda falaremos desse “nome”,
por favor, não me deixe esquecer).
}
Cliente
public static void main(String[] args) {
new ServerRMI();
}
Agora daremos início à construção de uma aplicação RMI cliente para o servidor que acabamos de desenvolver juntos.
Na Listagem 4, é apresentada a classe cliente.
24 www.mundoj.com.br
}
1SPGFTTPS+t3.*+/%*'VOEBNFOUPT
Copie o arquivo FacadeIntf.class para rmi_home/client/bin/mj/rmiserver/
intf para que possamos compilar a nossa classe cliente. Entre no diretório
rmi_home/client/src e execute o seguinte comando:
package mj.rmiclient;
javac -cp .:../bin/ -d ../bin/ mj/rmiclient/ClientRMI.java
public class ClientRMI {
Listagem 4: mj.rmiclient.ClientRMI
public ClientRMI(String host) {
Falta apenas um processo a fazer antes de executar nosso cliente: disponibilizar
no classpath da aplicação cliente o stub gerado para o servidor que desenvolvemos há pouco. Ou seja, você deve copiar o artefato FacadeImpl_Stub para
rmi_home/client/bin/mj/rmiserver/impl.
try {
/*
* procura por uma instância no servidor, passado na URL,
* através de um “nome” (que mistério!)
* utilizando o método lookup() da classe java.rmi.Naming.
*/
Object remoteObject = java.rmi.Naming.lookup(
host + mj.rmiserver.intf.FacadeIntf.JNDI_NAME );
Isso tornará a nossa árvore de diretórios para o cliente e servidor nas seguintes
estruturas demonstradas nas figuras 3 e 4, respectivamente.
/*
* fazemos um casting do objeto retornado pela instrução
* demonstrada na listagem 11.
* Para essa operação precisamos utilizar a classe
* javax.rmi.PortableRemoteObject para realizar uma chamada ao
* seu método narrow().
* A Classe javax.rmi.PortableRemoteObject é a super classe do
* nosso objeto remoto
* ou foi utilizada para exportar o objeto para o registro no servidor.
* O método narrow() assegura que o objeto retornado pelo lookup()
* está de acordo com a interface passada no segundo argumento,
* se a conversão não for apropriada, uma exceção será lançada.
*/
mj.rmiserver.intf.FacadeIntf facade = (mj.rmiserver.intf.FacadeIntf)
javax.rmi.PortableRemoteObject
.narrow( remoteObject, mj.rmiserver.intf.FacadeIntf.class );
Figura 3. Estrutura de diretórios e arquivos da nossa aplicação cliente.
/*
* realizamos uma chamada como se tivéssemos uma instância local.
*/
System.out.println(“Diga olá: “ + facade.sayHello() );
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println(“Uso: java ClientRMI <host>”);
System.exit( 0 );
}
new ClientRMI( “//” + args[0] + “/” );
}
Figura 4. Estrutura de diretórios e arquivos da nossa aplicação servidora.
Pronto! Com o servidor em funcionamento, basta entrar no diretório
rmi_home/client/bin e executar o seguinte comando::
java -cp . mj/rmiclient/ClientRMI localhost
Caso tudo tenha ocorrido como o esperado, você deve ter a seguinte
saída:
Diga olá: Hello World!
(Woohoo! Funcionou! :o)
}
Melhorando o nosso exemplo
Empacotando o servidor
Para facilitar a disponibilização de nosso servidor RMI, podemos empacotar
os bytecodes gerados em um arquivo JAR. Com esse propósito, vamos criar
um arquivo para explicitar qual classe deve ser executada.
Crie um arquivo texto de nome MANIFEST.MF dentro de rmi_home/server/
com o seguinte conteúdo: Main-Class: mj.rmiserver.ServerRMI
Agora vamos criar o pacote com a aplicação servidora. Entre no diretório
25
Professor J
1SPGFTTPS+t3.*+/%*'VOEBNFOUPT
rmi_home/server/bin e execute o seguinte comando:
nos mais variados tipos de serviços de nomes e diretórios.
jar -cvfm server.jar ../MANIFEST.MF .
Serviço de nomes
O comando acima criará o arquivo JAR server.jar dentro de rmi_home/server/bin.
Resta-nos executar o arquivo JAR que acabamos de criar. Entre no diretório
rmi_home/server/bin e execute o seguinte comando:
java -jar server.jar
Empacotando o cliente
Para facilitar a disponibilização de nosso cliente RMI, podemos empacotar os
bytecodes gerados em um arquivo JAR. Com esse propósito, vamos criar um
arquivo para explicitar qual classe deve ser executada.
Crie um arquivo-texto de nome MANIFEST.MF dentro de rmi_home/client/
com o seguinte conteúdo:
Main-Class: mj.rmiclient.ClientRMI
Agora vamos criar o pacote com a aplicação cliente. Entre no diretório rmi_
home/client/bin e execute o seguinte comando:
jar -cvfm client.jar ../MANIFEST.MF .
O comando acima criará o arquivo JAR client.jar dentro de rmi_home/client/
bin.
Resta-nos executar o arquivo JAR que acabamos de criar. Entre no diretório
rmi_home/client/bin e execute o seguinte comando:
java -jar client.jar localhost
E novamente, caso tudo tenha ocorrido como o esperado, você deve ter a
seguinte saída:
Um nome se refere a uma entidade como pessoa ou objeto. O fato de se usar
um nome é devido a ser mais usual e fácil.
Quando você faz uma solicitação, por exemplo, ao website www.mundoj.com.
br um Domain Name System (DNS) é responsável por traduzir aquela URL
ao endereço IP amarrado àquele nome. Outro bom exemplo é quando você
solicita à sua operadora telefônica o número do telefone de uma determinada
pessoa que você tem o nome.
Então o serviço de nomes (naming service) permite que seja amarrada alguma
“coisa” a um nome e também permite que seja feita uma busca por essa “coisa”
através de seu nome.
Serviço de diretórios
Um diretório é um tipo de objeto (coisa) de atenção particular, pois ele pode
conter atributos. Você pode procurar por um objeto que seja um diretório,
como por exemplo, um usuário de rede que possui como atributos o seu nome
de usuário e senha.
Exemplos de serviços de diretórios (directory service) são o Microsoft Active
Directory e o OpenLDAP.
A sua estrutura de trabalho é organizar os diretórios em uma hierarquia conhecida como árvore. Um organograma é um exemplo dessa estrutura.
Um serviço de diretórios se parece muito com bancos de dados. Em ambos,
podemos armazenar informações e/ou consultar informações já previamente
definidas. Muitos dos serviços de diretórios são implementados por bancos de
dados por trás das cenas.
Conclusão
Diga olá: Hello World!
Nós poderíamos não adicionar os artefatos do nosso servidor (FacadeIntf e FacadeImpl_Stub) diretamente no pacote cliente (client.jar), disponibilizando-os
em um pacote separado para facilitar a manutenção e redistribuição de ambas
as aplicações: cliente e servidor. Mas isso nos levaria a um assunto paralelo à
intenção inicial deste artigo.
Java Naming and Directory Interface
A JNDI API é responsável por fornecer um padrão para definição e localização
através de um “nome” de recursos como EJBs, conexões JDBC, usuários, entre
outros dentro de um determinado ambiente.
A JNDI resolve o que deveria ser um problema para programadores Java: fornecedores diferentes com técnicas diferentes para trabalho sobre o serviço de
nomes e diretórios.
Usando unicamente a JNDI fazemos operações sobre serviços de nomes e
diretórios de forma transparente. Isso significa que não nos importa saber
se estamos acessando um Lightweight Diretory Access Protocol (LDAP) ou
Network Information System (NIS) ou ainda Network Directory System (NDS).
Com isso, basta aprender uma única API para acessar informações que estejam
26 www.mundoj.com.br
A intenção era apresentar-lhe o uso prático da RMI e para tal nós tínhamos que ter uma noção básica da API JNDI.
Com este exemplo, foi mostrado que nada além da JDK, que possuímos,
é necessário para construir uma aplicação distribuída em Java.
Claro, dessa forma, é dever do programador implementar códigos para
tratamento de transações, segurança, requisições simultâneas e balanceamento de carga, entre outros, fugindo um pouco de manter a atenção
apenas no negócio do sistema.
Por isso, além de outras coisas, existem os containers. Eles permitem que
fiquemos focados apenas no negócio do sistema em questão.
Agradecimentos
A Henrique Winckler ([email protected]) e a equipe da Mundoj pela
SFWJTÍPEFTUFBSUJHPt
Referências
t "QSFOEB+BWBFNEJBT3PHFST$PEFOIFBEF-BVSB-FNBZ
t .BTUFSJOH&OUFSQSJTF+BWB#FBOT3JNB1BUFM4SJHBOFTI(FSBME#SPTFF.JDB4JMWFSman.
Download