使用Janino运行时动态编译Java源码并加载成Class使用
演示如何使用Janino框架编辑源代码并利用ClassLoader及Java反射技术调用编译好的类
使用Janino运行时动态编译Java源码并加载成Class使用
一、Janino简介
Janino是一个超小、超快的开源Java 编译器。Janino不仅可以像javac一样将一组Java源文件编译成一组字节码class文件,还可以在内存中编译Java表达式、代码块、类和.java文件,加载字节码并直接在JVM中执行。
有关Janino的更多简介请参考:Janino框架介绍。本文将介绍如何使用Janino在运行时动态编译Java源代码并加载成Class使用。
二、需求
在运行时将已知的源代码编译成字节码并加载成Class使用。该Java源代码引用的相关类均能在当前运行的JVM中找到。
三、代码实现(基于JDK1.8)
相关代码可在gitee仓库中janino-demo获取。
1、pom中引入lombok及janino依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.10</version>
</dependency>
2、准备两个待编译的类源码,其中一个包含内部类
package com.shikx.demo.janino.compiler;
/**
* @author Kaixuan Shi
* @since 2023/7/17
*/
public class HelloServiceSource {
public void sayHello() {
System.out.println("Hello world!");
}
}
package com.shikx.demo.janino.compiler;
/**
* @author Kaixuan Shi
* @since 2023/7/17
*/
public class OuterClassSource {
public void sayHello(String name) {
System.out.println("Hello, " + name + ", I'm parent class instance");
}
public class InnerClass {
public void sayHello(String name) {
System.out.println("Hello, " + name + ", I'm child class instance");
}
}
}
3、编写JavaSourceCode类,用于记录待编译的类名称及对应源码
import lombok.Data;
/**
* @author Kaixuan Shi
* @since 2023/7/17
*/
@Data
public class JavaSourceCode {
/**
* 类全名
*/
private String fullClassName;
/**
* 源代码
*/
private String sourceCode;
}
4、编写ClassLoaderService类,用于加载编译好的类
public class ClassLoaderService {
private static ClassLoader classLoader;
public static void setClassLoader(ClassLoader classLoader) {
ClassLoaderService.classLoader = classLoader;
}
public static Class<?> loadClass(String fullClassName) throws ClassNotFoundException {
return classLoader.loadClass(fullClassName);
}
}
5、编写CompileService类,提供批量编译类方法
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.util.ResourceFinderClassLoader;
import org.codehaus.commons.compiler.util.resource.MapResourceCreator;
import org.codehaus.commons.compiler.util.resource.MapResourceFinder;
import org.codehaus.commons.compiler.util.resource.Resource;
import org.codehaus.commons.compiler.util.resource.StringResource;
import org.codehaus.janino.ClassLoaderIClassLoader;
import org.codehaus.janino.Compiler;
import org.codehaus.janino.CompilerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Kaixuan Shi
* @since 2023/7/17
*/
public class CompileService {
private static final CompilerFactory compilerFactory = new CompilerFactory();
public void compile(List<JavaSourceCode> sourceCodes) {
Compiler compiler = (Compiler) compilerFactory.newCompiler();
Map<String, byte[]> classes = new HashMap<>();
compiler.setClassFileCreator(new MapResourceCreator(classes));
compiler.setIClassLoader(new ClassLoaderIClassLoader(CompileService.class.getClassLoader()));
Resource[] resources = new Resource[sourceCodes.size()];
for (int i = 0; i < sourceCodes.size(); i++) {
JavaSourceCode sourceCode = sourceCodes.get(i);
resources[i] = new StringResource(sourceCode.getFullClassName(), sourceCode.getSourceCode());
}
// 调用Janino编译服务
try {
compiler.compile(resources);
} catch (CompileException | IOException e) {
throw new RuntimeException(e);
}
// 将编译后的字节码class赋给ClassLoader,以使用此ClassLoader加载类
ClassLoader cl = new ResourceFinderClassLoader(
new MapResourceFinder(classes),
CompileService.class.getClassLoader()
);
ClassLoaderService.setClassLoader(cl);
}
}
代码中“compiler.setIClassLoader(new ClassLoaderIClassLoader(CompileService.class.getClassLoader()));”这一步很关键,Janino编译时将使用该ClassLoader获取待编译源代码中依赖的相关类。若省略此步骤,Janino将从bootClassPath、classPath、extensionDirectories中寻找(参考org.codehaus.janino.Compiler#getIClassLoader),一些项目并不会将所有jar包放在classPath中,这将导致在运行时获取不到待编译源代码中依赖的类而报错。
ResourceFinderClassLoader重写了
java.lang.ClassLoader#findClass方法,将从MapResourceFinder中获取待加载的类的字节码。
6、编写Demo,将2中准备好的两份类源码编译并加载,调用其方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author Kaixuan Shi
* @since 2023/7/17
*/
public class JaninoCompilerDemo {
public static void main(String[] args) {
//region 构建源代码
JavaSourceCode helloServiceSourceCode = new JavaSourceCode();
helloServiceSourceCode.setSourceCode("package org.csdn.qq_40820249.demo.janino.compiler;\n" +
"\n" +
"/**\n" +
" * @author Kaixuan Shi\n" +
" * @since 2023/7/17\n" +
" */\n" +
"public class HelloServiceSource {\n" +
"\n" +
" public void sayHello() {\n" +
" System.out.println(\"Hello world!\");\n" +
" }\n" +
"}");
helloServiceSourceCode.setFullClassName("org.csdn.qq_40820249.demo.janino.compiler.HelloServiceSource");
JavaSourceCode outerClassSourceCode = new JavaSourceCode();
outerClassSourceCode.setSourceCode("package org.csdn.qq_40820249.demo.janino.compiler;\n" +
"\n" +
"/**\n" +
" * @author Kaixuan Shi\n" +
" * @since 2023/7/17\n" +
" */\n" +
"public class OuterClassSource {\n" +
"\n" +
" public void sayHello(String name) {\n" +
" System.out.println(\"Hello, \" + name + \", I'm parent class instance\");\n" +
" }\n" +
"\n" +
" public class InnerClass {\n" +
" public void sayHello(String name) {\n" +
" System.out.println(\"Hello, \" + name + \", I'm child class instance\");\n" +
" }\n" +
" }\n" +
"}\n");
outerClassSourceCode.setFullClassName("org.csdn.qq_40820249.demo.janino.compiler.OuterClassSource");
List<JavaSourceCode> sourceCodes = new ArrayList<>(2);
sourceCodes.add(helloServiceSourceCode);
sourceCodes.add(outerClassSourceCode);
//endregion
//调用编译服务
CompileService compileService = new CompileService();
compileService.compile(sourceCodes);
//通过反射调用编译好的类
try {
//调用helloServiceSourceCode中的类
Class<?> helloServiceClass = ClassLoaderService.loadClass(helloServiceSourceCode.getFullClassName());
Method helloServiceSayHelloMethod = helloServiceClass.getDeclaredMethod("sayHello");
helloServiceSayHelloMethod.invoke(helloServiceClass.newInstance());
//调用outerClassSourceCode中的“OuterClassSource”外部类
Class<?> outerClass = ClassLoaderService.loadClass(outerClassSourceCode.getFullClassName());
Method outerClassSayHelloMethod = outerClass.getDeclaredMethod("sayHello", String.class);
outerClassSayHelloMethod.invoke(outerClass.newInstance(), "Janino");
//调用outerClassSourceCode中的“InnerClass”内部类
Class<?> innerClass = ClassLoaderService.loadClass(
"org.csdn.qq_40820249.demo.janino.compiler.OuterClassSource$InnerClass");
Method innerClassChildClassSayHelloMethod = innerClass.getDeclaredMethod("sayHello", String.class);
innerClassChildClassSayHelloMethod.invoke(
innerClass.getDeclaredConstructors()[0].newInstance(outerClass.newInstance()), "Janino");
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new RuntimeException(e);
}
System.out.println("完成Janino编译及调用演示");
}
}
这里有一点需要注意,就是内部类反射创建对象时,由于该内部类不是静态的,所以需要传入父类对象作为入参。
执行结果如下:
Hello world!
Hello, Janino, I'm parent class instance
Hello, Janino, I'm child class instance
完成Janino编译及调用演示
四、总结
以上代码简单演示了如何使用Janino框架编辑源代码并利用ClassLoader及Java反射技术调用编译好的类。实际应用场景一般要更复杂,可以针对不同的场景继续做封装,编译和加载类的核心就是这些。
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)