A chamada de métodos remotos permite que aplicações chamem métodos de determinado objeto encontrados remotamente, compartilhando recursos e processando a carga em outros computadores. Ao contrário de outros sistemas que para a execução remota requerem que somente os tipos de dados simples ou as estruturas definidas sejam passados aos métodos remotos. O RMI permite que qualquer tipo do objeto Java seja usado - mesmo em caso do cliente ou do usuário não ter conhecimento sobre este objeto. O RMI permite o cliente e o usuário para carregar dinâmicamente o objeto novo datilografa como necessário. Neste artigo, você aprenderá mais sobre o RMI.

Visão Geral

A chamada de métodos remotos (RMI) facilita a chamada de métodos entre diferentes máquinas virtuais (JVMs). JVMs é encontrada em computadores diferentes - contudo uma JVM pode invocar os métodos que pertencem a um objeto armazenado em outra JVM. Os métodos podem até mesmo passar objetos que uma outra máquina virtual nunca obteve antes, permitindo o carregamento dinâmico de novas classes quando necessário. Esta é uma característica poderosa!

Considere o seguinte cenário:

  • O desenvolvedor A escreve um serviço que execute alguma função útil. Atualiza regularmente este serviço, adicionando características novas e melhorando seu comportamento.
  • O desenvolvedor B deseja usar o serviço fornecido pelo desenvolvedor A. Entretanto, é inconveniente para A, estar sempre fornecendo para B um update com a nova versão do componente.

O Java RMI fornece uma solução muito fácil! Desde que o RMI pode dinamicamente carregar classes novas, o desenvolvedor B pode deixar que as atualizações do componente sejam controladas automaticamente pelo RMI. O desenvolvedor A coloca as classes novas em um diretório web, onde o RMI possa buscar as novas atualizações enquanto o componente é pedido.

Conexões feitas quando o cliente usa RMI
Figura 1. Conexões feitas quando o cliente usa RMI

Primeiramente, o cliente deve contatar um registro do RMI, e pede o nome do serviço. O desenvolvedor B não conhecerá a posição exata do serviço do RMI, mas sabe o suficiente para contatar o registro do desenvolvedor A. Isto apontará no sentido do serviço que quer se chamar, conseguindo dessa forma utilizar o componente requisitado.

O serviço do desenvolvedor A muda regularmente, assim o desenvolvedor B não tem uma cópia da classe. Mas não há com que se preocupar, porque o cliente rmi busca automaticamente a nova subclasse de um web server onde os dois desenvolvedores compartilham as classes. A nova classe é carregada na memória, e o cliente rmi está pronto para utiliza-la. Isto acontece de forma transparente para o desenvolvedor B – não sendo necessário nenhuma nova codificação par buscar as classes.

Escrevendo serviços RMI

Escrever seus próprios serviços do RMI pode ser uma pouco difícil no início, dessa forma começaremos  com um exemplo que não seja muito complicado. Nós criaremos um serviço que possa calcular o quadrado de um número, e a potência de dois números (238 por exemplo). Devido ao tamanho grande dos números, nós usaremos a classe de java.math.BigInteger para valores de retorno maiores que um inteiro ou um longo.

Escrevendo uma interface

A primeira coisa que precisamos fazer é uma interface. Uma interface é uma descrição dos métodos que nós permitiremos que os clientes remotos invoquem. Vamos considerar exatamente o que nós necessitamos.

  1. Um método que aceite como parâmetro um o inteiro, calcule o seu quadrado, e retorna um BigInteger
  2. public BigInteger quadrado ( int num1 );Um método que aceita dois parâmetros inteiros e calcula a potência com estes números, retornando um BigInteger
  3. public BigInteger potencia ( int num1, int num2 );

Uma vez decidido os métodos que farão parte do nosso serviço, temos que criar uma interface. Uma interface é um método que contém métodos abstratos; estes métodos devem ser implementados por uma outra classe. A seguir é apresentado o código fonte para nosso serviço que calcula o quadrado e a potência de um número qualquer.


import java.math.BigInteger;
import java.rmi.*; //
// Interface PowerService
//
// Interface para um serviço RMI que calcula o quadrado e a potência 
// de um número 
//public interface PowerService extends java.rmi.Remote {
        // Calcula o quadrado de um número
        public BigInteger quadrado ( int number ) throws RemoteException; 
// Calcula a potência de um número
        public BigInteger potencia  ( int num1, int num2)throws RemoteException;
}

Nossa interface herda de java.rmi.Remote, que indica que este é um serviço remoto. Nós fornecemos as definições do método para nossos dois métodos (quadrado e potencia), terminando assim a implementação da nossa interface. A etapa seguinte é implementar a interface, e desenvolver os métodos quadrado e potência.

Implementando a interface

Implementar a interface é um pouco mis complicado - nós realmente temos que escrever os métodos do quadrado e da potência! Não se preocupe se você não souber como calcular quadrados e a potencia, esta não é uma lição de matemática. O código que nós precisamos saber implementar é o construtor e o método principal.

Nós temos que declarar por padrão um construtor, mesmo quando nós não temos nenhum código de iniciação para nosso serviço. Isto porque nosso construtor padrão pode lançar uma java.rmi.RemoteException, de seu construtor pai em UnicastRemoteObject. Parece confuso? Não se preocupar, porque nosso construtor é extremamente simples.


public PowerServiceServer () throws RemoteException
	{
		super();
	}

Nossa execução do serviço também precisa ter um método principal. O método principal será responsável por criar uma instância de nosso PowerServiceServer, e registrar (ou o ligar-binding) o serviço com o RMI Registry. Nosso método principal atribuirá também um gerente da segurança ao JVM, para impedir qualquer surpresa das classes remotamente carregadas. Neste caso, um gerente da segurança não é realmente necessário, mas em sistemas mais complexos onde falsos clientes estarão usando o serviço, isto passa a ser importante.


public static void main ( String args[] ) throws Exception
    {
       // Atribuir um gerente da segurança, caso as classes dinâmicas   
       // forem carregadas
       if (System.getSecurityManager() == null)
            System.setSecurityManager ( new RMISecurityManager() );

       // Cria uma instância do serviço PowerServiceServer...
       PowerServiceServer svr = new PowerServiceServer();

       // ... e liga (bind) este ao RMI Registry
       Naming.bind ("PowerService", svr);

       System.out.println ("Service bound....");
}

Uma vez que os métodos quadrado e podencia são adicionados, nosso servidor rmi está completo. Está aqui o código fonte para o PowerServiceServer.


import java.math.*;
import java.rmi.*;
import java.rmi.server.*; //
// PowerServiceServer
//
// Servidor para o serviço RMI que calcula o quadrado e a potência
//public class PowerServiceServer extends UnicastRemoteObject
implements PowerService {    public PowerServiceServer () throws RemoteException
    {
        super();
    }     // Calcula o quadrado de um número
    public BigInteger quadrado ( int number ) throws RemoteException
    {
        String numrep = String.valueOf(number);
        BigInteger bi = new BigInteger (numrep);
        // Eleva o número ao quadrado         bi.multiply(bi);
        return (bi);
    }     // Calcula a potência de um número
    public BigInteger potencia ( int num1, int num2) throws RemoteException
    {
        String numrep = String.valueOf(num1);
        BigInteger bi = new BigInteger (numrep);
        bi = bi.pow(num2);
        return bi;
    }     public static void main ( String args[] ) throws Exception
    {
        // Atribuir um gerente da segurança, caso as classes dinâmicas 
        // forem carregadas
        if (System.getSecurityManager() == null)
            System.setSecurityManager ( new RMISecurityManager() );
        // Cria uma instância do serviço PowerServiceServer...
        PowerServiceServer svr = new PowerServiceServer();
        // ... e liga (bind) este ao RMI Registry
        Naming.bind ("PowerService", svr);
        System.out.println ("Serviço rodando....");
    }
}

Escrevendo o cliente RMI

Para que um bom serviço, se você não escrever um cliente para utilizá-lo? Escrever clientes é a parte fácil – todo o cliente tem que fazer uma chamada ao registro para obter uma referência ao objeto remoto, e logo após chamar seus métodos. Toda a comunicação entre as máquinas é feita pelo Java RMI, tornando a implementação do cliente RMI bastante simples.

Nosso cliente deve primeiramente atribuir um gerente de segurança, e então obter uma referência ao serviço. Note que o cliente recebe uma instância da interface que nós definimos mais cedo, e não a implementação desta interface.


// Atribuir um gerente de segurança
	 if (System.getSecurityManager() == null)
	 {
	 System.setSecurityManager   (new RMISecurityManager());
	 }
	 // Chamar (lookup) o Rmi registry para o serviço PowerService
	 PowerService service = (PowerService) Naming.lookup
	 ("rmi://" + args[0] + "/PowerService");

Para identificar um serviço, nós especificamos um URL do RMI. O URL contém o hostname em que o serviço é situado, e o nome lógico do serviço. Isto retorna uma instância de PowerService, que possa então ser usado como uma referência local do objeto.


// Chamar o método remoto
.out.println ("Answer : " + service.quadrado(valor));
// Chamar o método remoto
.out.println ("Answer : " +  
service.potencia(valor,potencia));

O desenvolvimento de clientes RMI é a parte a mais fácil da construção dos serviços distribuídos. No fato, há mais código para o menu da relação de usuário no cliente do que há para os componentes do RMI! Para manter as coisas simples, não há nada para validação dos dados, tenha assim cuidado ao incorporar números.

Este é o código fonte para o cliente RMI.


import java.rmi.*;
import java.rmi.Naming;
import java.io.*; //
//
// PowerServiceClient
//
//public class PowerServiceClient {
  public static void main(String args[]) throws Exception
   {
       // Verifica o argumento hostname€
       if (args.length != 1) {
           System.out.println("Syntax - PowerServiceClient host");
           System.exit(1);
       }        // Atribuir um gerente de segurança
       if (System.getSecurityManager() == null) {
           System.setSecurityManager(new RMISecurityManager());
       }       // Chamar o registry para PowerService
       PowerService service = 
(PowerService) Naming.lookup("rmi://" + args[0] + "/PowerService");       
DataInputStream din = new DataInputStream (System.in);        
for (;;) {
           System.out.println("1 – Calcular o quadrado");
            System.out.println("2 – Calcular a potência");
            System.out.println("3 - Sair"); 
System.out.println();
            System.out.print ("Escolha : ");             
String line = din.readLine();
            Integer choice = new Integer(line);            
int value = choice.intValue();            switch (value) {
               case 1:
                  System.out.print ("Número : ");
                 line = din.readLine();System.out.println();
                  choice = new Integer (line);
                  value  = choice.intValue(); 
// Chamar o método remoto
                 System.out.println("Answer : " + service.quadrado(valor));
                 break;
               case 2:
                  System.out.print ("Número : ");
                  line = din.readLine(); 
                  choice = new Integer (line);
                 value  = choice.intValue();
System.out.print ("Potência : ");
                  line = din.readLine();
                  choice = new Integer (line);
                  int power = choice.intValue();
                  // Chamar o método remoto
                  System.out.println("Answer : " +
service.potencia(valor, potencia));
                  break;
               case 3:
                  System.exit(0);
                  default :
                       System.out.println ("Opção inválida");
                  break;
            }
       }
   } 
}

Executando o cliente e o servidor

Nosso exemplo foi extremamente simples. Para executar o exemplo deste artigo, o cliente e o servidor terão uma cópia dos classfiles, mas alguns sistemas mais avançados podem compartilhar o código do usuário em um web server. Se seus sistemas fizerem isto, não se esqueça de ajustar a propriedade do sistema java.rmi.server.codebase para o diretório do web server em que suas classes são armazenadas!

Iniciando o rmiregistry

Para iniciar o registry, os usuários Windows devem fazer o seguinte (assumindo que seu diretório java\bin directory está na pasta atual):


start rmiregistry

Para iniciar o registry, usuários Unix devem fazer o seguinte:


rmiregistry &

Compile o servidor

Compile o servidor, e use a ferramenta rmic para cirar os arquivos stub.

Iniciando o servidor

Do diretório em que as classes estão localizadas, digite o seguinte:


java PowerServiceServer

Iniciando o cliente

Você pode rodar o cliente localmente, ou de outras máquinas. Neste ultimo caso, você precisará espeficar o nome da máquina onde o servidor está rodando. Se você estiver rodando na mesma máquina, o nome será localhost.


java PowerServiceClient localhost

Resumo

Java RMI é um mecanismo útil para a chamada de métodos de objetos remotos. Java RMI permite que uma máquina virtual java invoque métodos de outra máquina virtual java, e compartilhe qualquer tipo de objeto implementado Java, mesmo que o cliente ou o usuário nunca tenham tido acesso a esse tipo de objeto antes.