<dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8</version></dependency>
public class DynamicClassLoader extends URLClassLoader {public DynamicClassLoader(ClassLoader parent) {super(new URL[0], parent);}public static String getClassFullName(JavaClassObject javaClassObject) {String name = javaClassObject.getName();name = name.substring(1, name.length() - 6);name = name.replace("/", ".");return name;}public Class<?> defineClass(JavaClassObject javaClassObject) {String name = getClassFullName(javaClassObject);byte[] classData = javaClassObject.getBytes();return super.defineClass(name, classData, 0, classData.length);}}
import javax.tools.SimpleJavaFileObject;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.net.URI;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*/@Overridepublic OutputStream openOutputStream() throws IOException {return bos;}}
import org.apache.commons.io.IOUtils;import javax.tools.SimpleJavaFileObject;import java.io.LineNumberReader;import java.io.StringReader;import java.net.URI;public class CharSequenceJavaFileObject extends SimpleJavaFileObject {/*** CharSequence representing the source code to be compiled*/private String 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 code* source code to compile*/public CharSequenceJavaFileObject(String className, String code) {super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);this.content = code;}/*** Answers the CharSequence to be compiled. It will give the source code* stored in variable "content"*/@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) {return content;}/*** 获取某个位置的代码*/public String getLineCode(long line) {LineNumberReader reader = new LineNumberReader(new StringReader(content));int num = 0;String codeLine = null;try {while ((codeLine = reader.readLine()) != null) {num++;if (num == line) {break;}}} catch (Throwable ignored) {} finally {IOUtils.closeQuietly(reader);}return codeLine;}}
import javax.tools.FileObject;import javax.tools.ForwardingJavaFileManager;import javax.tools.JavaFileManager;import javax.tools.JavaFileObject;import java.io.IOException;import java.util.ArrayList;import java.util.Collections;import java.util.List;public class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {private JavaClassObject mainObject;private List<JavaClassObject> innerObjects = new ArrayList<JavaClassObject>();protected ClassFileManager(JavaFileManager fileManager) {super(fileManager);}public byte[] getJavaClass() {return mainObject.getBytes();}public JavaClassObject getJavaClassObject() {return mainObject;}@Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,FileObject sibling) throws IOException {// 最后一个是mainObjectmainObject = new JavaClassObject(className, kind);innerObjects.add(mainObject);return mainObject;}public List<JavaClassObject> getInnerClassJavaClassObject() {if (this.innerObjects != null && this.innerObjects.size() > 0) {int size = this.innerObjects.size();if (size == 1) {return Collections.emptyList();}return this.innerObjects.subList(0, size - 1);}return Collections.emptyList();}@Overridepublic void close() throws IOException {if (null != mainObject) {mainObject.delete();}super.close();}}
import org.apache.commons.lang3.StringUtils;import javax.tools.*;import java.io.File;import java.net.URL;import java.net.URLClassLoader;import java.util.ArrayList;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;public class DynamicJdkCompiler {private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([_a-zA-Z][_a-zA-Z0-9\\.]*);");private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([_a-zA-Z][_a-zA-Z0-9]*)\\s+");private final JavaCompiler compiler;private URLClassLoader parentClassLoader;private String classpath;protected String encoding = "UTF-8";public DynamicJdkCompiler() {compiler = ToolProvider.getSystemJavaCompiler();if (null == compiler) {throw new IllegalStateException("无法获取编译器!请用JDK,而不是JRE运行JAVA。");}this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();this.buildClassPath();}private void buildClassPath() {this.classpath = null;StringBuilder sb = new StringBuilder();for (URL url : this.parentClassLoader.getURLs()) {String p = url.getFile();sb.append(p).append(File.pathSeparator);}this.classpath = sb.toString();}public Class<?> compile(String code) {Matcher matcher = PACKAGE_PATTERN.matcher(code);String packageName = null;if (matcher.find()) {packageName = matcher.group(1);}matcher = CLASS_PATTERN.matcher(code);String className;if (matcher.find()) {className = matcher.group(1);} else {throw new IllegalArgumentException("No such class name in " + code);}String fullClassName = null;if (StringUtils.isNotBlank(packageName)) {fullClassName = packageName + "." + className;} else {fullClassName = className;}return doCompile(fullClassName, code);}public Class<?> doCompile(String fullClassName, String javaCode) {DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(diagnostics, null, null);ClassFileManager fileManager = new ClassFileManager(standardJavaFileManager);CharSequenceJavaFileObject jfile = new CharSequenceJavaFileObject(fullClassName, javaCode);try {List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();jfiles.add(jfile);List<String> options = new ArrayList<String>();options.add("-encoding");options.add(encoding);options.add("-classpath");options.add(this.classpath);JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);boolean success = task.call();for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {String errorCode = "compile fail: " + diagnostic.getMessage(null) + ". \r\nat " + fullClassName+ "(" + diagnostic.getLineNumber() + ")" + "\r\ncode:"+ jfile.getLineCode(diagnostic.getLineNumber());throw new java.lang.Error(errorCode);}}if (!success) {String error = compilePrint(diagnostics);// System.err.print(error);throw new IllegalStateException("Compilation failed. class: " + fullClassName + ", diagnostics: \r\n"+ error);}DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);for (JavaClassObject javaClassObject : fileManager.getInnerClassJavaClassObject()) {dynamicClassLoader.defineClass(javaClassObject);}JavaClassObject jco = fileManager.getJavaClassObject();Class<?> clazz = dynamicClassLoader.defineClass(jco);try {dynamicClassLoader.close();} catch (Exception e) {// log.error("dynamicClassLoader.close throws " + e.getClass().getSimpleName() + " !", e);}return clazz;} finally {try {jfile.delete();} catch (Exception e) {//log.error("jfile.delete throws " + e.getClass().getSimpleName() + " !", e);}try {fileManager.close();} catch (Exception e) {//log.error("fileManager.close throws " + e.getClass().getSimpleName() + " !", e);}}}protected String compilePrint(DiagnosticCollector<JavaFileObject> diagnostics) {StringBuilder result = new StringBuilder();for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {compilePrint(diagnostic, result);result.append("\r\n");}return result.toString();}private void compilePrint(Diagnostic<?> diagnostic, StringBuilder stringBuilder) {stringBuilder.append("Code:[" + diagnostic.getCode() + "]\n");stringBuilder.append("Kind:[" + diagnostic.getKind() + "]\n");stringBuilder.append("Position:[" + diagnostic.getPosition() + "]\n");stringBuilder.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");stringBuilder.append("End Position:[" + diagnostic.getEndPosition() + "]\n");stringBuilder.append("Source:[" + diagnostic.getSource() + "]\n");stringBuilder.append("Message:[" + diagnostic.getMessage(null) + "]\n");stringBuilder.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");stringBuilder.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");}}
测试类
public class TestJava {public String test(String param){System.out.println(param);return param;}}
测试方法
@Testpublic void doCompile() throws Exception {DynamicJdkCompiler dynamicJdkCompiler = new DynamicJdkCompiler();String java = "public class TestJava {\n" + "\n" + "\tpublic String test(String param){\n" + "\t\tSystem.out" +".println(param);\n" + "\t\treturn param;\n" + "\t}\n" + "\n" + "}";Class<?> compile = dynamicJdkCompiler.compile(java);Object obj = compile.newInstance();Method m = compile.getMethod("test", String.class);Object invoke = m.invoke(obj, "123456");System.out.println(invoke.toString());}
