Listar e instanciar classes de um diretório

9 respostas
icemagno

Pessoal,

Basicamente o que quero fazer é :

  1. Deixar que outra pessoa crie uma classe que implementa uma interface definida por mim, em um pacote definido por mim.
  2. Me enviar o .class desta classe ( não decidi se o fonte vem junto )
  3. Colocar estas classes juntas em um diretório.
  4. Conheço as assinaturas dos métodos, pois a interface é minha, mas o modo de implementação depende de cada um, gerando resultados diferentes.
  5. Interface ou Classe Abstrata ???
  6. Meu programa vasculha este diretório e lista as classes existentes lá.
  7. Escolho uma e carrego, chamo os métodos que quiser ( sei quais são ).

Encontrei esse trecho de código que carrega a classe de um arquivo, mas acho que além de ser pouco elegante na forma como chama o método ( gostaria de chamar da forma tradicional MyStuff.myMethod() ) dizem que fica carregada na VM ad eternum

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class Main {

	private static String classOutputFolder = "/home/diretorio/das/classes";
	
    public static void main(final String args[]) throws MalformedURLException {
    	runIt();
    }

    public static void runIt()
    {
        File file = new File(classOutputFolder);
        try
        {
            URL url = file.toURI().toURL(); 
            System.out.println("Caminho : " + url);
            URL[] urls = new URL[] { url };
 
            ClassLoader loader = new URLClassLoader(urls);
 
            Class<?> thisClass = Class.forName("math.Calculator", true, loader);
            
            Class<?> params[] = {};
            Object paramsObj[] = {};
            Object instance = thisClass.newInstance();
            Method thisMethod = thisClass.getDeclaredMethod("testAdd", params);
 
            thisMethod.invoke(instance, paramsObj);
            
        }
        catch (MalformedURLException e)
        {
        }
        catch (ClassNotFoundException e)
        {
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
    
 
}

Alguém conhece solução mais elegante ?

Grato.

PS: A classe que está sendo chamada :

package math;

public class Calculator implements ICalculator {
	
	public void testAdd() { 
		System.out.println(200+300); 
		
	}
	
}

9 Respostas

wagnerfrancisco

Me parece que você poderia usar os métodos diretamente mesmo neste código. Algo assim:

//...
Class<MinhaInterface> thisClass = Class.forName("math.Calculator", true, loader);

MinhaInterface instance = thisClass.newInstance();

instance.testAdd(parametros);
//...

Não executei, mas acredito que funcione.

icemagno

wagnerfrancisco:
Me parece que você poderia usar os métodos diretamente mesmo neste código. Algo assim:

//...
Class<MinhaInterface> thisClass = Class.forName("math.Calculator", true, loader);

MinhaInterface instance = thisClass.newInstance();

instance.testAdd(parametros);
//...

Não executei, mas acredito que funcione.

Obrigado, mas

type mismatch : Cannot convert from Class<capture#1-of?> to Class

wagnerfrancisco

Talvez algo assim:

Class<MinhaInterface> thisClass = Class.forName("math.Calculator", true, loader).asSubclass(MinhaInterface.class);
icemagno

wagnerfrancisco:
Talvez algo assim:

Class<MinhaInterface> thisClass = Class.forName("math.Calculator", true, loader).asSubclass(MinhaInterface.class);

Deu o mesmo erro brother …

icemagno

SOLUÇÃO:

Class<?> thisClass = Class.forName("math.Calculator", true, loader);
            
            Object instance = thisClass.newInstance();
            
            ICalculator calc = (ICalculator)instance;
            calc.testAdd();

Fonte:

http://www.daniweb.com/software-development/java/threads/27355/newinstance-loading-classes-during-runtime

Obrigado pela atenção amigão.

tveronezi

Uma forma elegante de se fazer isso é pelo uso do ServiceLoader (http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html)
Vc pode usar isso em conjunto com a solução do Classloader, se vc quiser carregar as classes que implementem a tua interface de outros jar que não sejam aqueles incluidos no teu classpath.

Basicamente vc cria uma interface “br.com.meuprograma.Fruta”.
Vc cria uma pasta “META-INF/services” no teu classpath com um arquivo texto chamado “br.com.meuprograma.Fruta” (o nome do arquivo deve ser isso mesmo)
Dentro desse arquivo vc lista todas as classes que implementem esse cara

br.com.outropacote.Abacaxi
br.com.maisumpacote.Banana
br.com.outropacote.Manga

Dentro do teu programa vc chama…

ServiceLoader<Fruta> loader = ServiceLoader.load(Fruta.class);
for (Fruta fruta : loader) {
  fruta.comer();
}

Dá uma olhada na API. Explica direitinho como se usa isso.

icemagno

tveronezi:
Uma forma elegante de se fazer isso é pelo uso do ServiceLoader (http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html)
Vc pode usar isso em conjunto com a solução do Classloader, se vc quiser carregar as classes que implementem a tua interface de outros jar que não sejam aqueles incluidos no teu classpath.

Basicamente vc cria uma interface “br.com.meuprograma.Fruta”.
Vc cria uma pasta “META-INF/services” no teu classpath com um arquivo texto chamado “br.com.meuprograma.Fruta” (o nome do arquivo deve ser isso mesmo)
Dentro desse arquivo vc lista todas as classes que implementem esse cara

br.com.outropacote.Abacaxi
br.com.maisumpacote.Banana
br.com.outropacote.Manga

Dentro do teu programa vc chama…

ServiceLoader<Fruta> loader = ServiceLoader.load(Fruta.class);
for (Fruta fruta : loader) {
  fruta.comer();
}

Dá uma olhada na API. Explica direitinho como se usa isso.

Hum.
Sem dúvida nenhuma é mais elegante.
Porém, eu não sei de antemão quais serão as implementações.
Basicamente é como no jogo ROBOCODE:

Eu digo: " Olha, tenho uma especificação para uma fruta que usarei para juntar com outras e fazer uma salada, se vocês quiserem, implementem da sua forma e me entregue."

Você pega a interface, implementa os métodos da maneira que lhe convier e dá a essa fruta o nome que desejar, contanto que respeite o pacote.

Eu pego a fruta que vc implementou e chamo os métodos. O comportamento da sua fruta será avaliado juntamente com o das outras, comparando certos parâmetros.

No caso do Robocode, eu digo :

TveroneziRobot.Ande(50);

Pode ter um comportamento diferente de:

IcemagnoRobot.Ande(50);

Você pode dar duas cambalhotas e cinco voltas antes de andar 50 pixels e eu posso andar 90 pixels e voltar 40 …

Mas sua solução é boa. Vou guardar caso precise. Obrigado.

icemagno

Olha pessoal, descobri que minha solução não funcionou. Tive essa impressão pq acabei esquecendo o arquivo da classe de exemplo no build path.
Ele não achava o arquivo quando eu usava

Class<?> thisClass = Class.forName("math.Calculator", true, loader);

e acabava buscando no build path.

Continuo na luta. Se alguem souber como fazer isso eu agradeço.

icemagno

Bom, agora finalmente eu consegui.

[img]http://www.cmabreu.com.br/java/tela.jpg[/img]

Podem baixar o fonte do loader em
[url]http://www.cmabreu.com.br/java/ClassLoader.zip[/url]

O JAR ( java -jar classloader.jar ):
[url]http://www.cmabreu.com.br/java/classloader.jar[/url]

As classes a serem carregadas:
[url]http://www.cmabreu.com.br/java/robots.zip[/url]

E os fontes das classes:
[url]http://www.cmabreu.com.br/java/fonte_classes.zip[/url]

A solução:

package br.com.cmabreu.loader;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import math.ICalculator;

public class Main {

	@SuppressWarnings("deprecation")
	public static void main(String[] args) throws MalformedURLException, 
							ClassNotFoundException, InstantiationException, IllegalAccessException {
		// Relativo à raiz do C:\ ( ou do drive do JAR )
		String diretorio = "/robots/";
		String pacote = "math";

		// As classes devem ficar nos diretórios de seus pacotes. 
		// Ex: Classe math.Calculator deverá ficar em /robots/math/Calculator.class
		File clsroot = new File(diretorio);
        URL url = clsroot.toURL();          
        URL[] urls = new URL[]{url};
        ClassLoader loader = new URLClassLoader(urls);		
		
		// Pesquisa no diretorio do pacote a procura das classes
		File clspath = new File(diretorio + pacote);
		if (clspath.isDirectory()) {
			for (File customclasse : clspath.listFiles()) {
				if (!customclasse.isDirectory()) {
						
						// Separa nome / extenção
						String nome = customclasse.getName();	
					    int dot = nome.lastIndexOf(".");
					    String nome_curto = nome.substring(0, dot);						
					    String extencao = nome.substring(dot + 1);
					    						
						if ( extencao.equals("class") ) {
							String classname = pacote + "." + nome_curto;
							System.out.println("Carregando classe " + classname + " em " + diretorio + pacote + "/" + nome);
							
							// Carrega a classe
							Class<?> cls =  Class.forName(classname, false, loader ) ;
			    	        
							// Instancia um objeto ( precisamos ter a interface conosco )
							Object obj = cls.newInstance() ;
			                ICalculator calc = (ICalculator)obj;
			                
			                // Chama o método testAdd()
			                calc.testAdd();  							
						
						}
					
				}
			}
		}
		

	}

}

Eu estava com as classes de exemplo em c:\robots\math

Abraços a todos.

Criado 25 de julho de 2012
Ultima resposta 14 de set. de 2012
Respostas 9
Participantes 3