Criando classes em tempo de execução

14 respostas
B

Olá galera, bom eu to meio sem tempo então vou logo ao assunto. Alguem sabe criar uma classe em tempo de execução? por exemplo, abro uma conexão com o repositorio de dados e pego a metadata da base, dai verifico os atributos de uma determinada entidade da base e com base nisso, em tempo de execução, crio uma classe referente aquela entidade da base com os seus atributos e todo o resto.

Quem sabe fazer isso, por reflexão já me falaram que da mas não quiseram me da a dica então sei que entre vos alguem sabe, se possivel gostaria que me ajuda-se com isso.

Att,

14 Respostas

V

Você quer criar uma classe ou um objeto em tempo de execução?

Uma classe ainda não dá… mas um objeto é possível fazer em tempo de execução.

S

balthazar:
Olá galera, bom eu to meio sem tempo então vou logo ao assunto. Alguem sabe criar uma classe em tempo de execução? por exemplo, abro uma conexão com o repositorio de dados e pego a metadata da base, dai verifico os atributos de uma determinada entidade da base e com base nisso, em tempo de execução, crio uma classe referente aquela entidade da base com os seus atributos e todo o resto.

É possivel criar definições de classes em runtime via manipulação de bytecode. Bibliotecas como a Javassist fazem isso. Só que não se pode criar uma classe do zero. Ou se implementa uma interface pre-definida ou se extende um classe pre-definida.
Por outro lado, mesmo que vc pudesse criar uma classe me runtime, seria inutil Para criar o programa vc não teria como se referir a essa classe (pois ela não está definia) então estaria sendo se referindo a alguma representação da classe que vc sinda não conhece ou usando reflection constantemente. Ou seja, é inutil construir uma classe so zero em runtime

D

Bem, eu já fiz algo parecido, não sei se funciona para ambiente web, mas para desktop era normal.

Primeiro eu criava um arquivo texto com o código que eu queria e depois
usava as classes de compilação do Java para compilar minha classe e por último carregava a mesma. Nessa minha classe eu tinha um método que fazia o que eu queria, então usando reflexão eu invocava o mesmo.

No Java 1.6 a API de compilação/carga é documentada (e é diferente da do java < 1.6), o que não acontecia nas versões anteriores.

Eu tenho uma classe que criei, faz uns 2 anos já, que usa a API de compilação do Java 1.5.

/**
 * Classe que define os métodos de compilação e
 * execução do código que será gerado e compilado.
 *
 * @author David Buzatto
 */


import com.sun.tools.javac.*;

import java.io.*;
import java.lang.reflect.*;
import java.net.*;

import javax.swing.*;


public class Engine {
	
	public Engine() {
		
		// inicializa o contador de classes
		contadorClasses = 0;
		
	}
	
	// método que realiza a compilação do código inserido
 	public boolean compilar( String codigo ) {
		
		// criando código da classe
		String codClasse = 
			  "/**\n"
			+ " * Classe gerada dinamicamente.\n"
			+ " *\n"
			+ " * @author David Buzatto\n"
			+ " */\n\n\n"
			+ "import static java.lang.Math.*;\n\n"
			+ "import java.math.*;\n\n\n"
			+ "public class Expressao" + contadorClasses + " {\n\n"
			+ "    public double calcular( double x ) {\n\n"
			+ "        return " + codigo + ";\n\n"
			+ "    }\n\n"
			+ "}";
			
		try {
			
			// deixa um log da compilacao num arquivo chamado logCompilacao.txt
			PrintWriter saida = new PrintWriter(
				new FileWriter( "logCompilacao.txt" ) );
			
			// grava o codigo-fonte no disco
			String arquivoFonte = "Expressao" 
				+ contadorClasses + ".java";
			FileWriter writer = new FileWriter( arquivoFonte );
			
			//grava no arquivo o código
			writer.write( codClasse );
			writer.close();
			
			// compila o código gerado,
			// saida é onde será gravada a saída do compilador
			int resultadoCompilacao = Main.compile(
				new String[] { arquivoFonte }, saida );
			
			if ( resultadoCompilacao == 0 ) {
				
				return true;
				
			} else {
				
				// lê o arquivo de resultados e imprime na tela
				BufferedReader resultado = new BufferedReader(
					new FileReader( "logCompilacao.txt" ) );
					
				String linha;
				
				while( ( linha = resultado.readLine() ) != null ) {
					
					System.out.println( linha + "\n" );
					
				}
				
				saida.close();
				
				return false;
				
			}
			
		} catch( IOException exc ) {
			
			System.out.println( "Erros ao gravar arquivo: \n" + exc.getMessage());
			exc.printStackTrace();
			
			return false;
			
		}
		
	}
	
	// executa a classe, selecionando o método de cálculo
	public double executar( double x ) {
		
		double resultado = 0;
		
		try {
			
			URLClassLoader ucl = URLClassLoader.newInstance(
				new URL[] {
				getClass().getResource( "Empressao" + contadorClasses ) } 
			);
			
			Class classe = ucl.loadClass( "Empressao" + contadorClasses );
			
			// cria instância da classe
			Object instancia = classe.newInstance();
			
			// obtém o método desejado
			Method metodoCalcular = classe.getMethod( 
				"calcular", double.class );
			
			// invoca o método, passando como parâmetro 
			// uma instância da classe que ele pertence
			resultado = ( Double ) metodoCalcular.invoke( 
				instancia, x );
			
			/*// referência a classe
			Class classe = Class.forName( "Expressao" 
				+ contadorClasses );
			
			// cria instância da classe
			Object instancia = classe.newInstance();
			
			// obtém o método desejado
			Method metodoCalcular = classe.getMethod( 
				"calcular", double.class );
			
			// invoca o método, passando como parâmetro 
			// uma instância da classe que ele pertence
			resultado = ( Double ) metodoCalcular.invoke( 
				instancia, x );*/
		
		} catch ( Exception exc ) {
			
			exc.printStackTrace();
			
		}
		
		// incrementa o contador de classes
		contadorClasses++;
		
		return resultado;
		
	}
	
	// conta quantas classes foram compiladas e carregadas
	private int contadorClasses;
	
}

Essa implementação tem um problema, pois toda vez que uma classe é gerada/compilada/carregada a mesma fica carregada na máquina virtual... Tenta dar uma pesquisada em como fazer a "descarga" da classe.

Até mais!

A

davidbuzatto:
Tenta dar uma pesquisada em como fazer a “descarga” da classe.
Puts, eu não aconselho. O cara, pra fazer o quê você está sugerindo, vai ter que mexer com gc e o classloader. Isso pode complicar muito e, IMO, não tem nada a ver com o problema dele.

Cara, você não poderia simplificar não? Tipo, construir seus sqls dinamicamente com os dados obtidos no metadata não seria suficiente?

T

Dependendo do que você quiser fazer, pode usar o seguinte:

  • Criar uma classe dinamicamente como o Buzzato indicou (criando um fonte Java e o compilando);
  • Criar uma classe a partir dos bytecodes (usando algum framework como o ASM, http://asm.objectweb.org )*;
  • Ou simplesmente criar um Map &lt String, Object &gt mapeando o nome do campo ao seu valor, e isso não envolve a criação de novas classes.

Se o seu problema fosse "quero criar uma classe dinamicamente que implementa determinada interface", poderia usar a classe java.lang.Proxy.

  • Uma vez eu experimentei fazer isso para criar um "Comparator" genérico, usando ASM. É legal mas dá trabalho demais, e é meio chato de fazer manutenção. Se você sabe que seu código deverá sofrer bastante manutenção posterior, nem tente usar uma solução dessas.
A

thingol:
- Criar uma classe dinamicamente como o Buzzato indicou (criando um fonte Java e o compilando);

mas ainda terá o problema da “descarga” da classe e suas dependências!
O ASM resolve isso?

B

Olá galera! bom, de repente eu esteja tentando fazer da maneira mas difícil ou complexa. Bom, vou deixar essa questão para mais tarde só que agora quero mostrar a vocês um outro problema que estou tendo. :roll:

Primeiramente eu to criando um objeto que e constantemente monitorado pelo sistema e caso seja executado qualquer método setter desse objeto, ele deve receber uma modificação de estado. O estado desse objeto e mapeado em uma classe Enum e por default o estado e clear mas caso ocorra um setter ele deve receber um dirty nesse estado.

Bem, eu construí um código de demonstração para expressa esse problema. Peguem e executem. Debuguem no para perceber qual o problema. 8)

Mas de ante-mão irei dizer. O método main retorna um objeto monitoravel utilizando o metodo Proxy_Test.create e altera o método setnome desse objeto de modo que o Proxy, através do metodo invoke, capture essa alteração. Daí ele realiza um teste simples para saber se o método executado e ou não um setter (essa e uma forma de verificação bem simples). Caso seja, o proxy deve altera o estado o objeto que esta sendo monitorado, entretanto, quando chega em setStateMethod.invoke(proxy, parameters) que e justamente a linha que executa a alteração do estado, essa alteração não ocorre o laço retorna para o if e o processo começa todo novamente. :oops:

Minha pergunta e bem simples. Onde ta meu erro? O que to fazendo que ta saindo errado? Ou melhor, quem consegue corrigir isso ai? :wink:

Como visto, o código abaixo ta completo ou pelo menos eu espero que esteja (qualquer coisa posso passá-lo novamente) e pode ser executado direto debugando pelo main e indo pelo invoke que e o cara que não deixa passar. Olhem abaixo…

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class Proxy_Test implements InvocationHandler, Serializable{
	
  protected Class dominioClass;
	

  protected Proxy_Test(Class dominioClass){
       this.dominioClass = dominioClass;
  }
	

  public static Object create(Class dominioClass){	
      return Proxy.newProxyInstance(dominioClass.getClassLoader(), new Class[] {	dominioClass  }, new Proxy_Test(dominioClass));
  }


  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, Exception {
      String methodName = method.getName();
		
      // verifica se e um metodo set e caso seja entao muda
      // o estado do objeto proxy
      if(methodName.startsWith("set")) {
			
         Class[] paramTypes = { EnumState.class };
         Object[] parameters = { EnumState.osDirty };
			
         // pega o metodo setsState do objeto proxy
         Method setStateMethod = proxy.getClass().getMethod("setState", paramTypes);			

         // executa o metodo setState passando o parametro desejado
         setStateMethod.invoke(proxy, parameters);		
		
         /*
         * nesse ponto e executado o getState para verificar se ele realmente foi modificado para EnumState.osDirty	  
         Method getStateMethod = proxy.getClass().getMethod("getState");
         System.out.println(getStateMethod.invoke(proxy));
         */
      }
      return null;
  }
	
  public static void main(String args[]) {
      Usuario b = (Usuario) Proxy_Test.create(Usuario.class);    	
      b.setNome("bobo da corte");

      System.out.println(b.getNome());		
      System.out.println(b.getState());
  }

  public enum EnumState{
     osClear, osDelete, osDirty;
  }

  public interface Dominio{
    public void setState(EnumState arg0);
    public EnumState getState();
  }

  public interface Usuario implements Dominio {
    public void setNome(String nome);
    public String getNome();
  }

}

Pessoal, espero que alguém ai me ajude pos realmente to precisando disso imensamente. Qualquer duvida me perguntem que terei maior prazer em responder. :lol:

Att,

L

Olá

Nem olhei seu código porque se um dia precisasse de monitorar um setter eu usaria AOP.

[]s
Luca

B

Programação pra aspectos correto?

De maneira bem simples, voce pode explicar como AOP funciona e como pode resolver meu problema.

:wink:

R

Cara você precisa criar Beans Dinamicos os malditos DynaBeans.
É facil e pratico

da uma lida nisso

http://www.devmedia.com.br/articles/viewcomp.asp?comp=3295&vt=-1

neste artigo tem um lugar que ele pega um arquivo .properties pra dizer quais os attibutos do bean, mas da pra substituir ele pelo resultado de uma proc ou resultset.

B

Realmente, a forma como os mini-aplicativos são criados é bastante interessante. Obrigado!

Att,

A

Precisei fazer isso para o meu TCC, use o Javassist, veja CtClass, CtField e CtMethod

Dica:

CtClass classe = ClassPool.getDefault().makeClass(“NomeDaClasse”);

P

Groovy?

J

ta… e se o caso for de criar objetos em tempo de execução?

O meu priblema é o mesmo do amigo que iniciou o tópico, mas eu quero criar um objeto que retorne os dados de uma linha de retorno de uma SQL, tipo como o mysql_fetch_object() do PHP faz.

Já tenho tudo certinho, basta o retorno (o principal… hehehe)

Alguém me ajuda, por favor!!!

Criado 15 de julho de 2007
Ultima resposta 12 de jan. de 2008
Respostas 14
Participantes 11