一、动态编译器实现类
package com.api.utils;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 动态编译器实现类
*
* @author zhanqi
* @since 2020-11-21 13:39:34
*
*/
public class DynamicConditionCompiler {
//类全名
private String fullClassName;
private String sourceCode;
//存放编译之后的字节码
private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
//获取java的编译器
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//存放编译过程中输出的信息
private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
//执行结果(控制台输出的内容)
private String runResult;
//编译耗时(单位ms)
private long compilerTakeTime;
//运行耗时(单位ms)
private long runTakeTime;
public DynamicConditionCompiler(String sourceCode) {
this.sourceCode = sourceCode;
this.fullClassName = getFullClassName(sourceCode);
}
/**
* 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
*
* @return true:编译成功 false:编译失败
*/
public boolean compiler() {
long startTime = System.currentTimeMillis();
//标准的内容管理器,更换成自己的实现,覆盖部分方法
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null);
JavaFileManager javaFileManager = new StringJavaFileManage(standardFileManager);
//构造源代码对象
JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName, sourceCode);
//获取一个编译任务
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticsCollector, null, null, Arrays.asList(javaFileObject));
//设置编译耗时
compilerTakeTime = System.currentTimeMillis() - startTime;
return task.call();
}
/**
* 执行main方法,重定向System.out.print
*/
public void runMainMethod(String methodName,Object... pramas) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, UnsupportedEncodingException {
PrintStream out = System.out;
try {
long startTime = System.currentTimeMillis();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(outputStream);
//PrintStream PrintStream = new PrintStream("/Users/temp.sql"); //输出到文件
System.setOut(printStream);
StringClassLoader scl = new StringClassLoader();
Class<?> aClass = scl.findClass(fullClassName);
Method method;
//invoke无参方法
if(pramas==null){
//调用main方法
if(methodName.equals("main")){
method = aClass.getDeclaredMethod("main", String[].class);
Object[] pars = new Object[]{1};
pars[0] = new String[]{};
method.invoke(null, pars);
}else {
method = aClass.getDeclaredMethod(methodName);
method.invoke(aClass.newInstance());
}
//invoke有参方法
}else{
Class<?>[] argTypes=new Class<?>[pramas.length];
for(int i=0;i<pramas.length;i++){
Object obj = pramas[i];
if(obj instanceof Class<?>){
argTypes[i]=Class.class;
}else if(obj instanceof String){
argTypes[i]=String.class;
}else if(obj instanceof Double){
argTypes[i]=Double.class;
}else if(obj instanceof Integer){
argTypes[i]=Integer.class;
}else if(obj instanceof Boolean){
argTypes[i]=Boolean.class;
}else if(obj instanceof List){
argTypes[i]=List.class;
}else if(obj instanceof Map){
argTypes[i]=Map.class;
}else if(obj instanceof ArrayList){
argTypes[i]=ArrayList.class;
}else if(obj instanceof LinkedHashMap){
argTypes[i]=LinkedHashMap.class;
}else if(obj instanceof LinkedList){
argTypes[i]=LinkedHashMap.class;
}else if(obj instanceof LinkedHashSet){
argTypes[i]=LinkedHashSet.class;
}else if(obj instanceof HashSet){
argTypes[i]=HashSet.class;
}else{
argTypes[i]=Object.class;
}
}
method = aClass.getDeclaredMethod(methodName,argTypes);
method.invoke(aClass.newInstance(),pramas);
}
//设置运行耗时
runTakeTime = System.currentTimeMillis() - startTime;
//设置打印输出的内容
runResult = new String(outputStream.toByteArray(), "utf-8");
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
//还原默认打印的对象
System.setOut(out);
}
}
/**
* @return 编译信息(错误 警告)
*/
public String getCompilerMessage() {
StringBuilder sb = new StringBuilder();
List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
for (Diagnostic diagnostic : diagnostics) {
sb.append(diagnostic.toString()).append("\r\n");
}
return sb.toString();
}
/**
* @return 控制台打印的信息
*/
public String getRunResult() {
return runResult;
}
public long getCompilerTakeTime() {
return compilerTakeTime;
}
public long getRunTakeTime() {
return runTakeTime;
}
/**
* 获取类的全名称
*
* @param sourceCode 源码
* @return 类的全名称
*/
public static String getFullClassName(String sourceCode) {
String className = "";
Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
Matcher matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className = matcher.group().replaceFirst("package", "").replace(";", "").trim() + ".";
}
pattern = Pattern.compile("class\\s+\\S+\\s+\\{");
matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className += matcher.group().replaceFirst("class", "").replace("{", "").trim();
}
return className;
}
/**
* 自定义一个字符串的源码对象
*/
private class StringJavaFileObject extends SimpleJavaFileObject {
//等待编译的源码字段
private String contents;
//java源代码 => StringJavaFileObject对象 的时候使用
public StringJavaFileObject(String className, String contents) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.contents = contents;
}
//字符串源码会调用该方法
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}
/**
* 自定义一个编译之后的字节码对象
*/
private class ByteJavaFileObject extends SimpleJavaFileObject {
//存放编译后的字节码
private ByteArrayOutputStream outPutStream;
public ByteJavaFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
}
//StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到outputStream)
@Override
public OutputStream openOutputStream() {
outPutStream = new ByteArrayOutputStream();
return outPutStream;
}
//在类加载器加载的时候需要用到
public byte[] getCompiledBytes() {
return outPutStream.toByteArray();
}
}
/**
* 自定义一个JavaFileManage来控制编译之后字节码的输出位置
*/
private class StringJavaFileManage extends ForwardingJavaFileManager {
StringJavaFileManage(JavaFileManager fileManager) {
super(fileManager);
}
//获取输出的文件对象,它表示给定位置处指定类型的指定类。
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className, kind);
javaFileObjectMap.put(className, javaFileObject);
return javaFileObject;
}
}
/**
* 自定义类加载器, 用来加载动态的字节码
*/
private class StringClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
if (fileObject != null) {
byte[] bytes = fileObject.getCompiledBytes();
return defineClass(name, bytes, 0, bytes.length);
}
try {
return ClassLoader.getSystemClassLoader().loadClass(name);
} catch (Exception e) {
return super.findClass(name);
}
}
}
/**
* 动态加载jar包
* @param jarPath
*/
public void loadJar(String jarPath) {
File jarFile = new File(jarPath);
// 从URLClassLoader类中获取类所在文件夹的方法,jar也可以认为是一个文件夹
Method method = null;
try {
method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException | SecurityException e1) {
e1.printStackTrace();
}
// 获取方法的访问权限以便写回
boolean accessible = method.isAccessible();
try {
method.setAccessible(true);
// 获取系统类加载器
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URL url = jarFile.toURI().toURL();
method.invoke(classLoader, url);
} catch (Exception e) {
e.printStackTrace();
} finally {
method.setAccessible(accessible);
}
}
}
二、实体类用于测试List<?>
package com.api.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* 维表实体类
*/
@Data
@ApiModel
public class DimInfo implements Serializable {
@ApiModelProperty(value = "查询出来的值")
private Map<String, Object> valueMap;
@ApiModelProperty(value = "sql参数")
private Map<String, Object[]> sqlParams;
}
三、测试运行
package com.api;
import com.api.entity.DimInfo;
import com.api.utils.DynamicConditionCompiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 动态编译器运行
*
* @author zhanqi
* @since 2020-11-21 13:46:12
*/
public class DynamicConditionTest {
public static void main(String[] args) {
String code = " import com.api.entity.DimInfo; " +
" import java.util.*;" +
" public class HelloWorld {\n" +
" public void myMethod(Integer j, List<DimInfo> list){\n" +
" System.out.println(\"j-> \"+j);\n" +
" System.out.println(\"list-> \"+list);\n" +
" }" +
" }";
DynamicConditionCompiler compiler = new DynamicConditionCompiler(code);
boolean res = compiler.compiler();
if (res) {
System.out.println("编译成功");
System.out.println("编译时间:" + compiler.getCompilerTakeTime());
try {
List<DimInfo> list = new ArrayList<>();
DimInfo dimInfo = new DimInfo();
Map<String, Object> map = new HashMap<>();
map.put("key", "詹啟");
dimInfo.setValueMap(map);
list.add(dimInfo);
compiler.runMainMethod("myMethod", 10, list);
System.out.println("运行时间:" + compiler.getRunTakeTime());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(compiler.getRunResult());
System.out.println("错误|警告信息:" + compiler.getCompilerMessage());
} else {
System.out.println("编译失败");
System.out.println(compiler.getCompilerMessage());
}
}
}
四、运行结果