Problemas com compilação dinâmica em Java

5 respostas Resolvido
java
G

Seguinte, preciso de uma classe que compile e execute código Java, diretamente na memória, sem gerar nenhum arquivo .java ou .class a partir de uma string que contenha o código.

Depois de pesquisar na internet, vi vários exemplos de utilização de classes do pacote javax.tools para este fim. Mas quase que a maioria não funcionava, e os que funcionavam não atendiam completamente o que eu precisava.

Nestas pesquisas, encontrei um exemplo simples, mas que supostamente atendia o que eu precisava. Este exemplo como a grande maioria, gerava muitos erros quando eu o executava, mas aos poucos fui conseguindo resolver a maioria deles. Segue meu código modificado abaixo.

import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.StringWriter;
    import java.lang.reflect.InvocationTargetException;
    import java.net.URI;
    import java.util.Arrays;
    import javax.tools.Diagnostic;
    import javax.tools.DiagnosticCollector;
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.SimpleJavaFileObject;
    import javax.tools.ToolProvider;
    import javax.tools.JavaCompiler.CompilationTask;
    import javax.tools.JavaFileObject.Kind;
    
    public class CompileSourceInMemory {
    	public static void main(String args[]) throws IOException {
    		System.setProperty("java.home", "C:/Program Files/Java/jdk1.8.0_102");
    		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    		System.setProperty("java.home", "C:/Program Files/Java/jre1.8.0_102");
    		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

		StringWriter writer = new StringWriter();
		PrintWriter out = new PrintWriter(writer);
		out.println("public class HelloWorld {");
		out.println("  public static void main(String[] args) {");
		out.println("    System.out.println(\"This is in another java file\");");
		out.println("  }");
		out.println("}");
		out.close();
		JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());

		Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
		CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);

		boolean success = task.call();
		for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
			System.out.println(diagnostic.getCode());
			System.out.println(diagnostic.getKind());
			System.out.println(diagnostic.getPosition());
			System.out.println(diagnostic.getStartPosition());
			System.out.println(diagnostic.getEndPosition());
			System.out.println(diagnostic.getSource());
			System.out.println(diagnostic.getMessage(null));

		}
		System.out.println("Success: " + success);

		if (success) {
			try {
				Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class }).invoke(null,
						new Object[] { null });
			} catch (ClassNotFoundException e) {
				System.err.println("Class not found: " + e);
			} catch (NoSuchMethodException e) {
				System.err.println("No such method: " + e);
			} catch (IllegalAccessException e) {
				System.err.println("Illegal access: " + e);
			} catch (InvocationTargetException e) {
				System.err.println("Invocation target: " + e);
			}
		}
	}
}

    class JavaSourceFromString extends SimpleJavaFileObject {
    	final String code;
    
    	JavaSourceFromString(String name, String code) {
    		super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
    		this.code = code;
	

    }

	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors) {
		return code;
	}
}

O código original pode ser encontrado aqui

Meu problema é que sempre que eu rodo esse código, ao invés de receber a seguinte saída

This is in another java file

no console, eu recebo

No such method: java.lang.NoSuchMethodException: HelloWorld.main([Ljava.lang.String;)

Alguém pode me explicar o porquê disso estar acontecendo, ou me dar uma luz com algum código que faça esse tipo de compilação?

5 Respostas

staroski
Solucao aceita

O problema acontece pelo fato de o .class da classe que você compilou, não estar no classpath do seu aplicativo em execução.

No seu exemplo a CompilationTask gera um arquivo em disco clamado “HelloWorld.class”, pois bem o diretório onde esse arquivo é gerado precisaria estar no classpath de sua aplicação.

Outra alternativa, é você usar um URLClassLoader que aponta para o diretorio onde o .class foi gerado e aí carregar o objeto Class a partir desse URLClassLoader.

Veja:

if (success) {
	try {
		File currentDir = new File(System.getProperty("user.dir"));
		URL[] classpath = new URL[] { currentDir.toURI().toURL() };
		URLClassLoader classLoader = new URLClassLoader(classpath);
		Class<?> loadedClass = classLoader.loadClass("HelloWorld");
		loadedClass.getDeclaredMethod("main", new Class[] { String[].class }).invoke(null, new Object[] { null });
	} catch (ClassNotFoundException e) {
		System.err.println("Class not found: " + e);
	} catch (NoSuchMethodException e) {
		System.err.println("No such method: " + e);
	} catch (IllegalAccessException e) {
		System.err.println("Illegal access: " + e);
	} catch (InvocationTargetException e) {
		System.err.println("Invocation target: " + e);
	}
}
G

Ah sim, muito obrigado! Funcionou aqui.
Mas gostaria de fazer essa compilação totalmente na memória, sem gerar aquivo nenhum, você saberia me dizer se tem como fazer isso?

peczenyj

cara eu acho que vc vai ter que implementar o seu proprio classloader

talvez este seja um bom ponto de partida:

staroski

Pra isso você vai ter de especializar um JavaFileManager para armazenar os bytes do .class em memória e, conforme nosso amigo @peczenyj comentou, terá de especializar um ClassLoader para conseguir obter instâncias de Class a partir dos bytes que você obteve com a compilação.

Veja este exemplo no StackOverflow: how to compile and run java source code in memory

G

Muito obrigado dnv, vou dar uma olhada nesses links e em como fazer isso. Maldito TCC, não era pra ser tão complicado kkkk

Criado 16 de outubro de 2016
Ultima resposta 18 de out. de 2016
Respostas 5
Participantes 3