Classes Remotas com ClassLoader

Este pequeno artigo irá demonstrar como utilizar classes que não se encontram presentes na aplicação e a partir delas instanciar objectos e manipula-los normalmente.

A classe abstracta ClassLoader é a responsável pelo carregamento das classes das nossas aplicações, entre outras funcionalidades ela permite a definição de classes, isto é a criação de objectos Class a partir de uma array de bytes que contem o corpo da classe.

Muitos devem pensar, “mas para que quero eu isso se tenho ali o ficheiro X.class??”. Podem existir muitas razões mas o que me levou a escrever este artigo e a meu meu o com fins mais práticos é o de termos classes remotas.

Imaginemos que temos um servidor com diversas classes, até mesmo centenas se quisermos ser amplos, e que essas classes pertencem a diversos programas diferente por exemplo o programa X precisa da classe Xa.class e Xb.class para poder funcionar. Em condições normais teríamos a nossa aplicação com essas três classes a X seria a main e a Xa e Xb classes lib para a X, nada de mais até agora. Mas se se o nosso programa tive-se apenas a classe X e cada vez que fosse executado fosse buscar “virtualmente” as classes que precisaria para trabalhar? Ou seja a nossa classe X iria ao servidor pedir a classe Xa e Xb, iria criar os objectos pretendidos destas classes e iria utiliza-los como pretendia nem que estas classes estivessem realmente no nosso computador.

E agora vem a pergunta, “e quais as vantagens ?? Afinal de contas teríamos de estar sempre a pedir as classes ao servidor, ficamos dependentes do servidor, da ligação e da carga no servidor!!!!”

Todos esses pontos são verdadeiros embora classes Java normalmente não seja muito “grandes”, poderia ser mau, caso o servidor fica-se congestionado ou caso a nossa aplicação precisar de algumas dezenas de classes podia gerar bastante tráfego na rede.

Mas e se forem apenas algumas classes, classes que sejam alteradas constantemente, classes que por segurança apenas seriam enviadas ao cliente depois de um processo de autenticação e sem o qual o programa não funcionaria ou até mesmo no caso de classes que não queremos simplesmente distribuir mas que são necessárias ao funcionamento do programa.

Aposto que aqui surgem muitas discussões umas contra outras a favor xD Cabe a cada um usar ou não este mecanismo, o objectivo aqui é apenas dar a conhecer que isso é possível e que funciona.

Para isso vamos utilizar um exemplo mesmo muito simples, um cliente, um servidor, um interface e uma classe a ser usada remotamente. Teremos então a seguinte estrutura.

.
|-- Cliente
|   |-- ClassesClient.java
|   `-- HelloInterface.java
`-- Servidor
    |-- ClassesServer.java
    |-- Hello.java
    `-- HelloInterface.java

Como podemos ver a parte do cliente apenas tem a classe principal e o interface da classe a ser usada remotamente. Por outro lado o servidor para além da classe principal e do interface tem ainda a classe remota que implementa o interface. Passemos agora ao código

/**
 *
 * @author magician
 */
public interface HelloInterface {

    //Imprime uma mensagem.
    public void printHello();

    //Imprime uma mensagem seguida do argumento
    public void printHello(String s);

    //Retorna uma String com uma mensagem.
    public String getHello();

}

[HelloInterface.java]

Este interface vai permitir ao cliente saber que métodos pode invocar da classe remota sem que ela esteja presente. Vejamos agora a classe Hello.java que implementa este interface.

/**
 *
 * @author magician
 */
public class Hello implements HelloInterface{

    //Imprime a mensagem "Hello xD"
    public void printHello() {
        System.out.println("Hello xD");
    }

    //Imprime a mensagem "Hello" seguida do argumento e por fim "xD"
    public void printHello(String s) {
        System.out.println("Hello " + s + " xD");
    }

    //Retorna uma String com a mensagem "Hello Remote xD"
    public String getHello() {
        return "Hello Remote xD";
    }
}

[Hello.java]

Esta será a classe remota, embora não esteja do lado do cliente ele irá utilizada, após a ter recebido do servidor sem a guardar fisicamente. Temos agora a classe ClassesServer que é o responsável por enviar a classe ao cliente.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author magician
 */
public class ClassesServer {

    public static void main(String args []){        
        try{
            //Carrega o conteúdo do ficheiro Hello.class para um array de bytes.
            FileInputStream in = new FileInputStream("Hello.class");
            byte [] b = new byte [in.available()];  
            int lido = in.read(b,0,b.length);

            //Aguarda pedidos na porta 3434
            ServerSocket sv = new ServerSocket(3434);
            Socket svc = null;
            while( (svc = sv.accept() ) != null){
                //A cada pedido envia o array de bytes correspondente à classe.
                svc.getOutputStream().write(b,0,lido);
                svc.close();
            }

        }
        catch(FileNotFoundException fnf){
            fnf.printStackTrace();
        }
        catch(IOException io){
            io.printStackTrace();
        }
    }   
}

[ClassesServer.java]

O servidor é mesmo muito simples apenas para exemplificar o funcionamento do processo. Em seguida temos a classe ClassesClient que faz o pedido ao servidor, define a classe e utiliza-a.

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 *
 * @author magician
 */
public class ClassesClient extends ClassLoader {

    public static void main(String args []){
        try{
            //Liga-se ao servidor.
            Socket cl = new Socket("localhost", 3434);
            InputStream in = cl.getInputStream();

            //Carrega a resposta enviada pelo servidor para um array de bytes.
            byte [] b = new byte [in.available()];
            int lidos = in.read(b, 0, b.length);

            in.close();
            cl.close();

            /* Através do método defineClasse existente na classe ClassLoader
             * dando o nome da classe e o array de bytes, como resultado e se
             * tudo correr correctamente teremos um objecto Class da classe Hello
             */
            Class c = new ClassesClient().defineClass("Hello", b, 0, lidos);

            /* Criamos um objecto da classe e fazemos um cast para o tipo do interface
             * que se encontra localmente presente.
             */
            HelloInterface objHello = ( (HelloInterface) c.newInstance());

            //Usamos os métodos do objecto normalmente.
            objHello.printHello();
            objHello.printHello("Magician");
            System.out.println(objHello.getHello());

        }
        catch(UnknownHostException uh){
            uh.printStackTrace();
        }
        catch(IOException io){
            io.printStackTrace();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

[ClassesClient.java]

Como devem ter reparado a nossa classe estende à classe ClassLoader, isso acontece porque a ClassLoad é abstracta e o método defineClass é protected.

Agora apenas falta testar, para isso basta compilar todos os ficheiro java, colocar o servidor a correr e em seguida fazer os pedidos com o cliente, a cada execução do cliente deverá surgir o seguinte output

Hello xD
Hello Magician xD
Hello Remote xD

Como podemos ver este output vem da classe Hello.java que não existem no cliente!!

Espero que tenha ajudado e que o artigo seja util, para saberem mais sobre a classe ClassLoader pode visitar a API em

http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

Por Fábio Correia (White Magician)

Anúncios

3 thoughts on “Classes Remotas com ClassLoader

  1. O meu não funciona da o seguinte erro

    Exception in thread “main” java.lang.NoClassDefFoundError: C:\User\CTIS\workspace\projetos\JavaApplication\ClassLoader\src\br\com\ctis\classloader\Hello (wrong name: br/com/ctis/classloader/Hello)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
    at br.com.ctis.classloader.ClassesClient.main(ClassesClient.java:28)

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google photo

Está a comentar usando a sua conta Google Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s