domingo, 19 de setembro de 2010

Execução de Classes Java via API de Reflexão.

INTRODUÇÃO

Nesse artigo, mostraremos como executar uma instância de uma classe Java usando a API de Reflexão do Java. Partiremos de um arquivo Java compilador (".class") passado como parâmetro. Esse arquivo ".class" será carregado por um carregador de classes personalizado, chamado de ClassLoader. Nesse caso, extenderemos a classe java.lang.ClassLoader para criarmos o nosso carregador de classes especifico com o nome de MyClassLoader. Durante a execução da aplicação, depois de carregado o arquivo ".class" passado como parâmetro, usaremos a API de Reflexão para executar um método estático dessa classe.


CARREGADOR DE CLASSES

Primeiramente, vamos criar um ClassLoader personalizado, chamado "MyClassLoader.java", veja a seguir:

Arquivo "MyClassLoader.java"
--------------------------------------------------------------------------------------------------------

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

public class MyClassLoader extends ClassLoader {

    private static final int MASK = 0x000000FF;
    private static final int MAGIC_CLASS = 0xCAFEBABE;

    private static final int CONSTANT_UTF8_INFO = 1;
    private static final int CONSTANT_INTEGER_INFO = 3;
    private static final int CONSTANT_FLOAT_INFO = 4;
    private static final int CONSTANT_LONG_INFO = 5;
    private static final int CONSTANT_DOUBLE_INFO = 6;
    private static final int CONSTANT_CLASS_INFO = 7;
    private static final int CONSTANT_STRING_INFO = 8;
    private static final int CONSTANT_FIELDREF_INFO = 9;
    private static final int CONSTANT_METHODREF_INFO = 10;
    private static final int CONSTANT_INTERFACE_METHODREF_INFO = 11;
    private static final int CONSTANT_NAMEANDTYPE_INFO = 12;

    private Map> mapClasses = new HashMap>();

    public MyClassLoader() {

    }

    /**
     * @param name
     *            Nome da Classe ou Nome do Recurso.
     */
    protected synchronized Class loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // Verifica se a classe já foi carregada
        Class c1 = mapClasses.get(name);

        if (c1 == null) {
            // Nova classe.

            // Retorna se for uma classe de sistema.
            try {
                return findSystemClass(name);

            } catch (ClassNotFoundException e) {
            }

            // Carrega os bytes através do nome do recurso.
            byte[] classBytes = loadClassBytes(name);

            if (classBytes == null) {
                throw new ClassNotFoundException(name);
            }

            // Através do bytecodes, descobrir o nome da classe!
            String className = getClassName(classBytes);

            c1 = defineClass(className, classBytes, 0, classBytes.length);

            if (c1 == null) {
                throw new ClassNotFoundException(name);
            }

            // Nome do recurso - Classe.
            mapClasses.put(name, c1);

            // Nome da classe - Classe.
            mapClasses.put(className, c1);

            if (resolve) {
                resolveClass(c1);
            }
        }

        return c1;
    }

    /**
     * Implementação padrão.
     * 
     * Procurar dentro da pasta scripts. Usar caminho relativo.
     * 
     * 
     */
    private byte[] loadClassBytes(String name) {
        try {
            FileInputStream in = new FileInputStream(name);

            if (in != null) {
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();

                int ch = in.read();

                while (ch != -1) {
                    buffer.write((byte) ch);

                    ch = in.read();
                }

                in.close();

                return buffer.toByteArray();
            }

        } catch (IOException e) {
        }

        return null;
    }

    /**
     * Localizar nome de arquivo através dos bytecodes.
     * 
     */
    private String getClassName(byte b[]) {
        // return "Test";
        int j = 0;

        Map mapIndexAndObject = new HashMap();

        int aux = (MASK & b[j]) << 24;
        aux = aux | (MASK & b[j + 1]) << 16;
        aux = aux | (MASK & b[j + 2]) << 8;
        aux = aux | MASK & b[j + 3];

        if (aux != MAGIC_CLASS) {
            throw new IllegalArgumentException("O arquivo class inválido! ");
        }

        int constant_pool_count = (MASK & b[j + 8]) << 8 | MASK & b[j + 9];

        j = j + 10;

        for (int i = 1; i < constant_pool_count; i++) {

            switch (MASK & b[j]) {
            case CONSTANT_UTF8_INFO: {
                j++;

                int length = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

                j = j + 2;

                Reader reader;

                try {
                    reader = new InputStreamReader(new ByteArrayInputStream(b,
                            j, length), "UTF-8");
                } catch (UnsupportedEncodingException e1) {
                    e1.printStackTrace();

                    // Usa o padrão (UTF-16) se não existir.
                    reader = new InputStreamReader(new ByteArrayInputStream(b,
                            j, length));
                }

                j = j + length;

                char cbuf[] = new char[length];
                try {
                    reader.read(cbuf);

                } catch (IOException e) {
                    e.printStackTrace();
                }

                mapIndexAndObject.put(i, new String(cbuf));

                try {
                    reader.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
                ;
                break;
            case CONSTANT_INTEGER_INFO: {
                j = j + 5;
            }
                ;
                break;
            case CONSTANT_FLOAT_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_LONG_INFO: {
                j = j + 9; 

                i++;
            }
                ;
                break;
            case CONSTANT_DOUBLE_INFO: {
                j = j + 9; 

                i++;
            }
                ;
                break;
            case CONSTANT_CLASS_INFO: {
                j++;

                aux = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

                mapIndexAndObject.put(i, aux);

                j = j + 2;
            }
                ;
                break;
            case CONSTANT_STRING_INFO: {
                j = j + 3;
            }
                ;
                break;
            case CONSTANT_FIELDREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_METHODREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_INTERFACE_METHODREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_NAMEANDTYPE_INFO: {
                j = j + 5;

            }
                ;
                break;
            }
        }

        j = j + 2;

        aux = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

        int index = (Integer) mapIndexAndObject.get(aux);

        return (String) mapIndexAndObject.get(index);
    }
}

--------------------------------------------------------------------------------------------------------

Nessa implementação, vamos sobrescrever o seguinte método da classe ClassLoader: 

protected synchronized Class loadClass(String name, boolean resolve)  throws ClassNotFoundException

Esse método recebe como parâmetro o nome qualificado (Pacote + Nome da Classe) da classe a ser carregada. O objetivo desse método é apresentar essa classe a JVM com a chamada ao seguinte método:

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

E também, o ClassLoader personalizado deve administrar as classes que foram carregadas, através do uso de um mapa (Objeto cuja classe implementa a interface java.util.Map) relacionando o nome da classe com a classe carregada, por exemplo. 
Se caso a classe carregada for classe do sistema ou da API do Java, é chamado o seguinte método:

protected final Class findSystemClass(String name) throws ClassNotFoundException

Esse método deve ser executado no começo do processo de carregamento de uma classe, se está for de sistema, o ClassLoader personalizado não deve carregá-la e esse método retornará a classe do sistema já carregada. Se a classe não for do sistema, será lançada a exceção java.lang.ClassNotFoundException com o intuito de indicar que a classe não foi localizada. Nesse caso, o nosso ClassLoader não deve tratar a exceção e partir para o processo de carregamento da classe em questão.

Se a flag "resolve" do método "loadClass(String name, boolean resolve)" estiver configurada com Verdadeiro, esse método deve chamar o seguinte método da superclasse:

protected final void resolveClass(Class c) 

Esse método carregará as outras classes que a classe carregada depende. Mas a chamadas ao método "loadClass(String name, boolean resolve)" são feitas pela JVM e não pela aplicação. 


API DE REFLEXÃO JAVA

Nesse momento já temos o nosso carregador de classes personalizado. A seguir temos um programa que lê da linha de comandos o arquivo ".class" a ser carregado pelo nosso carregador e depois, de posse com o objeto da classe java.lang.Class localizamos o método estático "main" da classe carregada, chamando o seguinte método:

Method m = c.getMethod("main", new Class[] { args.getClass() });

A variável "c" é a classe carregada contendo o método estático "main" cujo argumentos é um array de java.lang.String!
Nesse chamada, temos o método a ser invocado pela chamada a seguir:

m.invoke(null, new Object[] { args });

Essa chamada invocará o método localizado na classe carregada!
A seguir, temos o programa que fará essa execução:

Arquivo "ExecutionTest.java"
--------------------------------------------------------------------------------------------------------
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ExecutionTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 1) {
            throw new IllegalArgumentException(
            "Deve especificar o nome do arquivo");
        }

        ClassLoader classLoader = new MyClassLoader();

        Class c = null;

        try {
            c = classLoader.loadClass(args[0]);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // Faz invocação do método estático "main"
        Method m = null;

        // Localiza método estático main.

        try {
            m = c.getMethod("main", new Class[] { args.getClass() });

        } catch (SecurityException e) {
            e.printStackTrace();

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        // Executa método estático main.
        try {
            m.invoke(null, new Object[] { args });

        } catch (IllegalArgumentException e) {
            e.printStackTrace();

        } catch (IllegalAccessException e) {
            e.printStackTrace();

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

--------------------------------------------------------------------------------------------------------

Além desse arquivo, abaixo temos um arquivo java de teste que deve ser compilado para ser usado no programa ExecutionTest.

Arquivo "Test.java"
--------------------------------------------------------------------------------------------------------

public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Arquivo compilado e executado corretamente!!");
    }
}

--------------------------------------------------------------------------------------------------------

Para executar esse exemplo nesse artigo, basta compilar a classe "Test.java" e o resultado que é o arquivo "Test.class" deve ser usado como argumento na execução do programa "ExecutionTest" lembrando que o "MyClassLoader.class" deve estar no classpath desse programa.


REFERÊNCIAS


http://en.wikipedia.org/wiki/Java_Classloader

http://www.javaworld.com/javaworld/jw-10-1996/jw-10-indepth.html

http://download-llnw.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html

http://download.oracle.com/javase/tutorial/reflect/

Nenhum comentário:

Postar um comentário