Introdução á Computação Distribuida com RMI
A tecnologia RMI - Remote Method Invocation (Invocação de Métodos Remotos), foi primeiramente introduzida no Java, no JDK versão 1.1, elevando a programação para redes em um patamar mais elevado. Apesar do RMI ser relativamente fácil, ele põe o desenvolvedor Java frente à um novo paradigma, o mundo da computação de objetos distribuídos. Este guia prático vai lhe introduzir à esta tecnologia versátil, que melhorou muito desde sua primeira versão.
Objetivo
O principal objetivo para os criadores (designers) do RMI era permitir os programadores a desenvolverem programas distribuídos em Java com a mesma sintaxe e semântica usada em programas não-distribuídos. Para isso, eles tiveram que mapear cuidadosamente como classes Java e objetos trabalham em uma única Java Virtual Machine (JVM) para um novo modelo de como as classes e objetos trabalhariam num ambiente distribuído de computação (múltiplas JVMs). Os arquitetos do RMI tentaram fazer com que o uso dos objetos distribuídos em Java fosse similar ao uso de objetos Java locais. Esta seção introduz a arquitetura RMI da perspectiva dos objetos Java remotos distribuídos, e explora as diferenças de comportamento com objetos locais. A arquitetura RMI define como os objetos se comportam, como e quando exceções podem ocorrer, como a memória é gerenciada e como os parâmetros são passados e retornados de métodos remotos.
Arquitetura Java RMI
A arquitetura RMI estende a segurança e robustez da arquitetura Java para o mundo da computação distribuída.
Interfaces: O coração do RMI
A arquitetura RMI é baseada em um importante princípio: a definição do comportamento e a implementação do comportamento são conceitos separados. RMI permite que o código que define o comportamento e o código que implementa o comportamento permanecerem separados e rodarem em JVMs separadas. Em RMI, a definição do serviço remoto é codificada usando uma interface Java. A implementação do serviço remoto é codificada em uma classe. Logo, a chave para se entender o RMI é lembrar que as interfaces definem o comportamento e as classes definem a implementação. A classe que implementa o comportamento roda do lado do servidor RMI. A classe que roda no cliente atua como um Proxy para o serviço remoto. Veja o seguinte diagrama:
O programa cliente faz chamadas de métodos pelo objeto Proxy, o RMI envia a requisição para a JVM remota e redireciona para a implementação. Qualquer valor retornado pela implementação é devolvido ao Proxy e então ao programa cliente.
Arquitetura de Camadas do RMI
Com o entendimento da arquitetura RMI num alto nível, vamos dar uma breve olhada na sua implementação. A implementação do RMI é essencialmente feita de três camadas de abstração. A camada Stub e Skeleton está abaixo dos olhos do desenvolvedor. Esta camada intercepta as chamadas de métodos feitas pelo cliente para que a variável de referência da interface redirecione essas chamadas para o serviço RMI remoto. A próxima camada é a Remote Reference Layer. Esta camada sabe como interpretar e gerencias referências feitas dos clientes para os objetos do serviço remoto. A conexão do cliente ao servidor é Unicast (uma-para-um). A camada de transporte é baseada nas conexões TCP/IP entre as maquinas em uma rede. Usando essa arquitetura de camadas, cada uma das camadas poderia ser facilmente melhorada ou substituída sem afetar o resto do sistema. Por exemplo, a camada de transporte poderia ser substituída por uma camada que implemente conexões UDP/IP, sem afetar as camadas superiores. 
Nomeando Objetos Remotos
Como um cliente acha o serviço remoto RMI? Os clientes acham os serviços remotos usando o serviço de nomeação ou diretório (naming or directory). Isso parece um pouco redundante, mas o serviço de nomeação ou diretório roda como um endereço bem formado (host:port). O RMI pode usar diferentes tipos de serviços de diretório, incluindo o JNDI. O próprio RMI inclue um simples serviço, chamado de RMI Registry. O RMI Registry roda em cada maquina que hospeda o serviço remoto, por definição na porta 1099. Numa máquina host, um programa servidor cria um serviço remoto, primeiramente criando o objeto que implemente aquele serviço. Em seguida ele exporta aquele objeto para o RMI. Quando o objeto é exportado o RMI cria um serviço que aguarda as conexões do cliente. O servidor registra o objeto no RMI Registry, com um nome público. No lado do cliente o RMI Registry é acessado através da classe estática Naming. Ela provém o método lookup( ), que o cliente usa para requisitar o registro. Esse método aceita a URL que especifica o nome do servidor e o nome do serviço desejado. O método retorna uma referência remota para o objeto do serviço. A URL é formada como seguinte:
Usando o RMI
Agora vamos trabalhar com um sistema que realmente implementa um sistema com RMI. Vamos criar um aplicativo simples, cliente e servidor, que executa métodos do objeto remoto. Para tanto não necessitamos de duas máquinas distintas ou com IP distintos. O exemplo pode ser rodado na mesma máquina, pois o RMI sabe como trabalhar com isso, mesmo que o host e o cliente sejam na mesma localidade. Um sistema RMI é composto de várias partes:
Definição das interfaces para os serviços remotos
Implementações dos serviços remotos
Arquivos de Stub e Skeletons
Um servidor para hospedar os serviços remotos
Um serviço de RMI Naming que permite o cliente achar os serviços remotos
Um provedor de arquivos de classes (servidor http ou ftp)
Um programa cliente que necessita os serviços remotos
Criando seu aplicativo com RMI
Agora iremos, de fato, criar um sistema que implemente o RMI, utilizando-se de um programa cliente e um programa servidor. Não utilizaremos um servidor FTP ou HTTP, no entanto utilizaremos os programas na mesma máquina e uma mesma estrutura de diretórios. Os passos a serem seguidos agora são:
Escrever e compilar o código Java da interface
Escrever e compilar o código Java das implementações das classes
Gerar as classes Stub e Skeleton das classes de implementação Crie um diretório para salvar todos os seus arquivos de projeto. Você pode fazer o download do código fonte usado nesse tutorial.
Interfaces
O primeiro passo, como dito, será criar a interface e compilá-la. A interface define todas as funcionalidades remotas oferecidas pelo serviço. Nomeio o arquivo como: Mensageiro.java.
- import java.rmi.Remote;
- import java.rmi.RemoteException;
-
- public interface Mensageiro extends Remote {
-
- public void enviarMensagem( String msg ) throws RemoteException;
- public String lerMensagem() throws RemoteException;
- }
Perceba que esta interface estende a classe Remote, e cada assinatura de método declara as funcionalidades do serviço, e que podem gerar uma exceção RemoteException. Salve este arquivo (Mensageiro.java) no seu diretório e compile, com a seguinte linha de comando:
Implementação
Agora, você deverá escrever a implementação para o serviço remoto, ou seja, o código a ser executado no ambiente remoto. Nomeia o arquivo como: MensageiroImpl.java.
- import java.rmi.RemoteException;
- import java.rmi.server.UnicastRemoteObject;
-
- public class MensageiroImpl extends UnicastRemoteObject implements Mensageiro {
-
- public MensageiroImpl() throws RemoteException {
- super();
- }
-
- public void enviarMensagem( String msg ) throws RemoteException {
- System.out.println( msg );
- }
-
- public String lerMensagem() throws RemoteException {
- return "This is not a Hello World! message";
- }
- }
Salve este arquivo (MensageiroImpl.java) no seu diretório e compile, com a seguinte linha de comando:
- javac MensageiroImpl.java
Observe que a classe se utiliza (estende) da classe UnicastRemoteObject para linkar com o sistema RMI. Neste exemplo a classe estende a classe UnicastRemoteObject diretamente. Isto não é realmente necessário, mas essa discusão fica para uma próxima etapa. Quando uma classe estende a classe UnicastRemoteObject, ele deve prover um construtor que declare que ele pode lançar uma exceção RemoteException, pois quando o método super( ) é chamado, ele ativa o código em UnicastRemoteObject, que executa o link RMI e a iniciação do objeto remoto.
Stubs e Skeletons
Gere os arquivos Stubs e Skeletons da classe de implementação que roda no servidor. Para tanto, execute o comando rmic, compilador RMI do JDK.
Após a execução deste comando, você deveria ver no seu diretório os arquivos Mensageiro_Stub.class, Mensageiro_Skeleton.class. Servidor O serviço remoto RMI deve ser hospedado em um processo servidor. A classe MensageiroServer é um servidor bem simples, que provê serviços essenciais. Salve o arquivo como: MensageiroServer.java.
- import java.rmi.Naming;
-
- public class MensageiroServer {
-
- public MensageiroServer() {
- try {
- Mensageiro m = new MensageiroImpl();
- Naming.rebind("rmi://localhost:1099/MensageiroService", m);
- }
- catch( Exception e ) {
- System.out.println( "Trouble: " + e );
- }
- }
-
- public static void main(String[] args) {
- new MensageiroServer();
- }
- }
Salve este arquivo (MensageiroServer.java) no seu diretório e compile, com a seguinte linha de comando: > javac MensageiroServer.java
Cliente
O código fonte para o cliente é o seguinte. Salve o arquivo como: MensageiroClient.java.
- import java.rmi.Naming;
- import java.rmi.RemoteException;
- import java.rmi.NotBoundException;
- import java.net.MalformedURLException;
-
- public class MensageiroClient {
-
- public static void main( String args[] ) {
- try {
- Mensageiro m = (Mensageiro) Naming.lookup( "rmi://localhost/MensageiroService" );
- System.out.println( m.lerMensagem() );
- m.enviarMensagem( "Hello World!" );
- }
- catch( MalformedURLException e ) {
- System.out.println();
- System.out.println( "MalformedURLException: " + e.toString() );
- }
- catch( RemoteException e ) {
- System.out.println();
- System.out.println( "RemoteException: " + e.toString() );
- }
- catch( NotBoundException e ) {
- System.out.println();
- System.out.println( "NotBoundException: " + e.toString() );
- }
- catch( Exception e ) {
- System.out.println();
- System.out.println( "Exception: " + e.toString() );
- }
- }
- }
Salve este arquivo (MensageiroClient.java) no seu diretório e compile, com a seguinte linha de comando:
- javac MensageiroClient.java
Rodando o sistema RMI
Agora que todos os arquivos do projeto de exemplo foram criados e devidamente compilados, estamos prontos para rodar o sistema! Você precisará abrir três diferentes consoles do MS-DOS no seu Windows, ou outro, caso utilize um diferente sistema operacional. Em um dos consoles vai rodar o programa servidor, no outro o cliente e no terceiro o RMI Registry. Inicie com o RMI Registry. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Execute a seguinte linha de comando:
Isso irá iniciar o RMI Registry e rodá-lo. No segundo console vamos executar o programa servidor. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Execute o seguinte comando:
Isso irá iniciar, carregar a implementação na memória e esperar pela conexão cliente. No último console, rode o programa cliente. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Excute o comando:
Se tudo correr bem, que é o que esperamos e o que deveria acontecer, a seguinte saída será gerada nos consoles 2 (servidor) e 3 (cliente). No console 2 (servidor):
No console 3 (cliente):
- This is not a Hello World! message
É isso aí. Você acabou de criar um sistema utilizando a tecnologia RMI. Apesar de você ter rodado os programas na mesma máquina, o RMI usa a pilha de rede TCP/IP para se comunicar entre as três diferentes instâncias da JVM. Espero que tenham gostado e aprendido com esse pequeno exemplo de como se usar o RMI.
Fonte:Daniel Destro