Есть интерес в создании откомпилированного класс-файла прямо из кода.
Например, в методе main()
используем следующий код:
File file = new File("C:/.../out/production/projectName/SomeClass.class");
file.createNewFile();
Мы получили пустой файл SomeClass.class
.
Теперь необходимо в него что-нибудь записать.
OutputStream os = new FileOutputStream(file);
os.write("public".getBytes());
Такой подход не работает. На выходе имеем файл, который можно прочитать лишь блокнотом. Из среды разработки файл SomeClass.class
пустой.
Как записать в файлы .class байткод, который будет понимать JVM?
Во-первых, надо знать формат class-файлов. Во-вторых, надо знать байткод и правила его написания. Желательно знать принципы работы вирутальной машины. После освоения всего этого, можно взять инструмент манипуляции байткодом, вроде ASM, и написать собственный "компилятор"
import java.io.FileOutputStream;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
public class HelloClass {
public static void main(String[] args) throws Exception {
ClassWriter cw=new ClassWriter(0);
cw.visit(V1_6, ACC_PUBLIC+ACC_SUPER, "sample/HelloGen", null, "java/lang/Object", null);
// Конструктор по умолчанию
{
MethodVisitor mv=cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
// Метод main
{
MethodVisitor mv=cw.visitMethod(ACC_PUBLIC+ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC,"java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
// Сохраняем байткод на диск
FileOutputStream out=new FileOutputStream("/tmp/sample/HelloGen.class");
out.write(cw.toByteArray());
out.close();
}
}
Или не погружаться слишком глубоко в велосипедостроение и использовать Compiler API:
import java.net.URI;
import java.util.ArrayList;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
/**
* Класс эмулирующий для компилятора файлы исходного кода
* и позволяющий компилировать код прямо из памяти
*/
class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public 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;
}
}
public class CompilerDemo {
private static final ClassLoader classLoader = ToolProvider.getSystemToolClassLoader();
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/**
* Метод формирующий абстрактное синтаксическое дерево
* и преобразующий его в исходный код
*/
private static String generateSource() {
Context ctx = new Context();
JavacFileManager.preRegister(ctx);
TreeMaker treeMaker = TreeMaker.instance(ctx);
JavacElements elements = JavacElements.instance(ctx);
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
// Тело генерируемого метода
JCTree.JCBlock methodBody = treeMaker.Block(0, List.of(
treeMaker.Exec(
treeMaker.Apply(
List.<JCTree.JCExpression>nil(),
treeMaker.Select(
treeMaker.Select(
treeMaker.Ident(
elements.getName("System")
),
elements.getName("out")
),
elements.getName("println")
),
List.<JCTree.JCExpression>of(
treeMaker.Literal("Hello, World!")
)
)
)
)
);
// Определение генерируемого метода
JCTree.JCMethodDecl method = treeMaker.MethodDef(
modifiers,
elements.getName("sayHi"),
treeMaker.Type(new Type.JCVoidType()),
List.<JCTree.JCTypeParameter>nil(),
List.<JCTree.JCVariableDecl>nil(),
List.<JCTree.JCExpression>nil(),
methodBody,
null
);
// Определение генерируемого класса
JCTree.JCClassDecl tree = treeMaker.ClassDef(
modifiers,
elements.getName("Example"),
List.<JCTree.JCTypeParameter>nil(),
null,
List.<JCTree.JCExpression>nil(),
List.of(method)
);
return tree.toString();
}
/**
* Метод компилирующий исходный код
*/
public static void compile(Iterable<? extends JavaFileObject> compilationUnits) {
CompilationTask task = compiler.getTask(null, null, null, null, null, compilationUnits);
task.call();
}
public static void main(String[] args) {
java.util.List<JavaFileObject> sources = new ArrayList<>();
// Генерируем исходный код класса
sources.add(new JavaSourceFromString("Example", generateSource()));
// Компилируем сгенерированный код
compile(sources);
}
}
Во-первых, надо делать flush()
на OutputStream
, чтобы данные сохранились.
Во-вторых, чтобы получить валидный .class
файл, надо скомпилировать валидный Java код. А это не просто записать набор байтов в файл. Затем полученный Class
можно сохранить в файл, или можно сразу загрузить и исполнить его. Да, это можно сделать из кода на Java. Совсем недавно на Хабре была статья, как это делать в последних версиях Java. Ну и погуглив тему компиляции java кода из java кода дальше, Вы обнаружите тысячи примеров.
Виртуальный выделенный сервер (VDS) становится отличным выбором