Let’s suppose you have some string containing a source code for a class, something like “public class Test {}”. You want to compile it dynamically in memory and get an instance of that class. How can you accomplish that? We will try to present here a way to do it. We will use the javax.tools package that was added in Java 6, so be sure that you are using Java 6 or later to run the codes.
Although we will try to show the dynamic compilation it in the simplest way possible, we will still need to create four classes. You can get the full version of all the classes in a zipped archive here.
First let’s see the main method to get the feeling of what is going on:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: |
public class DynaCompTest { public static void main(String[] args) throws Exception { // Full name of the class that will be compiled. // If class should be in some package, // fullName should contain it too // (ex. "testpackage.DynaClass") String fullName = "DynaClass"; // Here we specify the source code of the class to be compiled StringBuilder src = new StringBuilder(); src.append("public class DynaClass {\n"); src.append(" public String toString() {\n"); src.append(" return \"Hello, I am \" + "); src.append("this.getClass().getSimpleName();\n"); src.append(" }\n"); src.append("}\n"); System.out.println(src); // We get an instance of JavaCompiler. Then // we create a file manager // (our custom implementation of it) JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); JavaFileManager fileManager = new ClassFileManager(compiler .getStandardFileManager(null, null, null)); // Dynamic compiling requires specifying // a list of "files" to compile. In our case // this is a list containing one "file" which is in our case // our own implementation (see details below) List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>(); jfiles.add(new CharSequenceJavaFileObject(fullName, src)); // We specify a task to the compiler. Compiler should use our file // manager and our list of "files". // Then we run the compilation with call() compiler.getTask(null, fileManager, null, null, null, jfiles).call(); // Creating an instance of our compiled class and // running its toString() method Object instance = fileManager.getClassLoader(null) .loadClass(fullName).newInstance(); System.out.println(instance); } } |
As you see the code we want to compile is stored in the variable src. After we define it, we print it to the console, get an instance of the compiler, put the source code into an object representing a source file, create a file manager and a compilation task. The real compilation starts when we call the call() method of the compilation task. Then we get the Class representing our compiled class from the file manager, instantiate our class and print it to the console, using the toString() function that we implemented in the code.
There are three classes used in the code that are not available in the JDK and hence we have to implement them by ourselves – CharSequenceJavaFileObject,JavaClassObject and ClassFileManger. Let’s see them one by one to understand what they are doing.
CharSequenceJavaFileObject implements the SimpleJavaFileObject interface and represents the source code we want to compile. Normally instances ofSimpleJavaFileObject would point to a real file in the file system, but in our case we want it to represent a StringBuilder createdy by us dynamically. Let’s see how it goes:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: |
public class CharSequenceJavaFileObject extends SimpleJavaFileObject { /** * CharSequence representing the source code to be compiled */ private CharSequence content; /** * This constructor will store the source code in the * internal "content" variable and register it as a * source code, using a URI containing the class full name * * @param className * name of the public class in the source code * @param content * source code to compile */ public CharSequenceJavaFileObject(String className, CharSequence content) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } /** * Answers the CharSequence to be compiled. It will give * the source code stored in variable "content" */ @Override public CharSequence getCharContent( boolean ignoreEncodingErrors) { return content; } } |
It stores an object of type CharSequence which is an interface implemented byStringBuilder. As you see in the code of our main function, we create an instance of the CharSequenceJavaFile providing it with src variable, which is of typeStringBuilder. The constructor stores it in the content private variable. It will be used when the compiler calls the getCharContent() method to get the source code to compile.
Next we must define the class representing the output of the compilation – compiled byte code. It is needed by the ClassFileManager which we will describe later. Compiler takes the source code, compiles it and splits out a sequence of bytes which must be stored somewhere. Normally they would be stored in a .class file but in our case we just want to make a byte array out of it. Here is a class that fulfills our needs:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: |
public class JavaClassObject extends SimpleJavaFileObject { /** * Byte code created by the compiler will be stored in this * ByteArrayOutputStream so that we can later get the * byte array out of it * and put it in the memory as an instance of our class. */ protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); /** * Registers the compiled class object under URI * containing the class full name * * @param name * Full name of the compiled class * @param kind * Kind of the data. It will be CLASS in our case */ public JavaClassObject(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); } /** * Will be used by our file manager to get the byte code that * can be put into memory to instantiate our class * * @return compiled byte code */ public byte[] getBytes() { return bos.toByteArray(); } /** * Will provide the compiler with an output stream that leads * to our byte array. This way the compiler will write everything * into the byte array that we will instantiate later */ @Override public OutputStream openOutputStream() throws IOException { return bos; } } |
At some point of the compilation, compiler will call openOutputStream() method of our JavaClassObject class and write there the compiled byte code. Because theopenOutputStream() method returns a reference to the bos variable, everything will be written there, so that afterwards we will be able to get the byte code from it.
We will also need something like a “file manager” that will tell the compiler to put the compiled byte code into an instance of our JavaClassObject class instead of putting it to a file. Here it is:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: |
public class ClassFileManager extends ForwardingJavaFileManager { /** * Instance of JavaClassObject that will store the * compiled bytecode of our class */ private JavaClassObject jclassObject; /** * Will initialize the manager with the specified * standard java file manager * * @param standardManger */ public ClassFileManager(StandardJavaFileManager standardManager) { super(standardManager); } /** * Will be used by us to get the class loader for our * compiled class. It creates an anonymous class * extending the SecureClassLoader which uses the * byte code created by the compiler and stored in * the JavaClassObject, and returns the Class for it */ @Override public ClassLoader getClassLoader(Location location) { return new SecureClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = jclassObject.getBytes(); return super.defineClass(name, jclassObject .getBytes(), 0, b.length); } }; } /** * Gives the compiler an instance of the JavaClassObject * so that the compiler can write the byte code into it. */ @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { jclassObject = new JavaClassObject(className, kind); return jclassObject; } } |
Function getClassLoader() will be called by us to get a ClassLoader instance for instantiating our compiled class. It returns an instance of SecureClassLoader modified by the function findClass(), which in our case gets the compiled byte code stored in the instance of JavaClassObject, defines a class out of it with the functiondefineClass() and returns it.
Now that we have all our classes ready, lets compile them and run the program. We should get an output like this:
public class DynaClass { public String toString() { return "Hello, I am "+this.getClass().getSimpleName(); } } Hello, I am DynaClass |
As you see, the toString() method of our dynamically compiled class was invoked printing “Hello, I am DynaClass” to the screen. Nice, isn’t it?
One of the questions that appear now is – What is that all good for? Are there any real life uses of it or is it just a nice toy? Well, classes compiled in this way can contain dynamic expressions that are not known until runtime. At the same time those classes benefit from all the optimizations that the Java compiler provides. One pretty straightforward use of this is when the user types in the program an expression like “y=2*(sin(x)+4.0)” and expects to see some output of it – for example a graph. Using dynamic compilation you don’t have to parse it any more by yourself, you could just compile it and get a fast, optimized function representing this expression. You can read about it (and much more about dynamic compilation generally) here.
Some other usage is creating dynamic classes for accessing data stored in JavaBeans. Normally you would have to use reflection for it, but reflection is very slow and its generally better to avoid using it when possible. Dynamic compilation allows you to minimize the use of reflection in a library that handles JavaBeans. How? We will try to show it in one of our next posts, so stay tuned!
相关推荐
### SQL Server 2016 In-Memory Optimization OLTP Enhancements #### In-Memory OLTP 简介 SQL Server 2016 引入了 In-Memory OLTP(在线事务处理)作为其核心特性之一,旨在显著提高数据库系统的性能。此特性...
Java动态编译指的是在程序运行时将Java源代码编译为字节码并加载到Java虚拟机(JVM)中的过程。这种技术在许多场景下非常有用,例如在开发环境中进行快速迭代、AOP(面向切面编程)或运行时代码生成等。Java的`javax...
2、Modern.Compiler.Implementation.in.Java.Second.Edition.chm Last year you may have seen the Modern Compiler Implementation in C: Basic Techniques (1997) which was the preliminary edition of our new ...
这篇博客“Java类动态加载(一)——java源文件动态编译为class文件”可能主要探讨了如何在运行时将Java源代码(.java)编译成对应的字节码文件(.class),并将其加载到Java虚拟机(JVM)中。以下是对这个主题的详细解析...
资源出自全球知名的半导体公司ADI(Analog Devices Inc.),这意味着书中涵盖的内容不仅有深厚的理论基础,还融入了业界领先的实践经验和设计思想。 模拟电路是电子工程中的基础学科,它涉及电阻、电容、电感、...
在Java编程中,动态编译代码并热加载类是一项重要的技术,它允许程序在运行时修改或添加新的类,而无需重启应用。这种能力对于快速迭代开发、调试和性能优化非常有用。本主题将深入探讨Java中的动态编译与热加载机制...
linux软件编译,安装。_Linux-Software-compilation
首先,Java提供了JavaCompiler接口,它是javax.tools包的一部分,允许我们在程序运行时动态编译Java源代码。以下是一些关键步骤和概念: 1. **获取Java工具提供商**:通过ToolProvider类,我们可以获取到Java...
增量 Maven 编译 样本输出 [INFO] --- takari-lifecycle-plugin:1.10.2:compile (default-compile) @ maven-incremental-compilation --- [INFO] Performing incremental build [INFO] Compiling 2 sources to /...
总的来说,通过JavaCompiler API,开发者可以轻松地在Java应用程序中实现动态编译的功能,这对于需要在运行时生成或修改代码的项目来说是一大利器。不过,需要注意的是,这种编译方式可能会增加程序的复杂性,并且在...
在C#编程中,动态编译是一个非常有用的特性,它允许程序在运行时生成和编译代码。这种技术主要用于各种场景,例如插件系统、代码生成、自动化测试等。本篇将详细介绍两种C#中实现动态编译的方法,并通过一个名为`...
与静态编译语言如C++不同,Java的优化主要发生在运行时,通过JVM的动态编译,即即时编译(JIT,Just-In-Time Compilation)。 动态编译的概念始于早期JVM对解释执行的改善。解释器逐行解释字节码,效率较低。为解决...
Compilation in the HotSpot VM 是一篇关于 Java 虚拟机(Java Virtual Machine,JVM)的技术文档, 由 Oracle 公司的 HotSpot 编译器团队的 Zoltán Majó 撰写。该文档详细介绍了 HotSpot JVM 中的编译过程,包括...
(4)提高对于交叉编译(cross-compilation)和交叉测试(cross-testing)的支持 【目前了解到的海思交叉编译工具链的应用环境】 arm-hisiv100-linux为基于uclibc的工具链,arm-hisiv200-linux 为基于 glibc 的工具...
在使用IntelliJ IDEA(简称Idea)进行Java开发时,有时会遇到“Error:java: Compilation failed: internal java compiler error”的错误提示,这通常意味着编译过程中遇到了内部错误,可能是由于配置问题、环境问题...
世界风java源码很棒的开源情报 精选的令人惊叹的开源智能工具和资源列表。 是从公开来源收集的情报。 在情报界 (IC) 中,“公开”一词是指公开的、公开的来源(与秘密或秘密来源相对) :open_book: 目录 贡献 如果您...
This article provides an overview of recent developments in mainmemory database systems. With growing memory sizes and memory prices dropping by a factor of 10 every 5 years, data having a “primary ...
2. **QGIS-Compilation-Guide**: 编译指南详细介绍了如何从源代码编译QGIS,这对于开发者来说是必不可少的。这里可能包含构建环境的设置、依赖库的安装、编译选项的选择等。通过这个指南,你可以了解软件工程中的...
根据Java编译器的优化的机制,Java也能够提供条件编译。对于条件设为false的语句,编译器将不对条件覆盖的代码段生成字节码。 不仅可以用简单的boolean常量值来做条件编译,还可以用字符串常量及任何其他类型的...
为了提高性能,JVM还引入了即时编译(JIT,Just-In-Time Compilation),它会在运行时将热点代码编译成本地机器码,从而获得接近原生应用的性能。 **六、垃圾收集** Java程序的一大优势是自动内存管理,其中垃圾...