1.背景
在某些应用中,我们的代码可能需要动态去执行...
意思就是说,你可以传一段代码去执行
比如:假设我们的业务需要对接上游渠道,而这个上游渠道非常多,随着业务的发展随时都在增加渠道....
这就意味这个,如果不能动态加载代码的话,每上一个渠道,我们就要打包发布一次项目...这样很麻烦,而却也不安全...容易出错
直接代码
2.实现代码
package com.XXX.cashier.hw; import javax.tools.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URI; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * * @Date: 2021-06-22 11:48 * @Description: */ public class CustomStringJavaCompiler { //类全名 private String fullClassName; private String sourceCode; //存放编译之后的字节码(key:类全名,value:编译之后输出的字节码) 也可以存放在磁盘上 private static Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>(); //获取java的编译器 private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //存放编译过程中输出的信息 private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>(); //编译耗时(单位ms) private long compilerTakeTime; public CustomStringJavaCompiler(String sourceCode) { this.sourceCode = sourceCode; this.fullClassName = getFullClassName(sourceCode); } /** * 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息 * * @return true:编译成功 false:编译失败 */ public boolean compiler() { long startTime = System.currentTimeMillis(); Object o = javaFileObjectMap.get(fullClassName); if (o != null) { //设置编译耗时 compilerTakeTime = System.currentTimeMillis() - startTime; System.out.println("使用已有的编译............"); return true; } //标准的内容管理器,更换成自己的实现,覆盖部分方法 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; Boolean call = task.call(); return call; } /** * 执行send 方法 */ public String runMainMethod(String json) throws Exception { StringClassLoader stringClassLoader = new StringClassLoader(); Class<?> obj = stringClassLoader.findClass(fullClassName); Constructor<?> constructor = obj.getConstructor(); Object newInstance = constructor.newInstance(); // 获取方法并执行 Method method01 = obj.getMethod("send", String.class); Object invoke = method01.invoke(newInstance, json); System.out.println("执行结束-->" + invoke); return invoke.toString(); } /** * @return 编译信息(错误 警告) */ public String getCompilerMessage() { StringBuilder sb = new StringBuilder(); List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics(); for (Diagnostic diagnostic : diagnostics) { sb.append(diagnostic.toString()).append(" "); } return sb.toString(); } public long getCompilerTakeTime() { return compilerTakeTime; } /** * 获取类的全名称 * * @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); } } } }
3.测试
@Test public void test() { // 这段代码可以是数据库或者磁盘文件读取 String code = "package com.XXXXX.cashier.order.testCode; " + "import com.alibaba.fastjson.JSONObject; " + "import lombok.extern.slf4j.Slf4j; " + "@Slf4j " + "public class Demo01 { " + " public String send(String json) { " + " log.info("json=" + json); " + " log.info("run method01-------------"); " + " JSONObject jsonObject = JSONObject.parseObject(json); " + " String orderNO = jsonObject.getString("orderNo"); " + " JSONObject object = new JSONObject(); " + " object.put("code", "100"); " + " object.put("msg", "success"); " + " object.put("orderNo", orderNO); " + " object.put("createTime", System.currentTimeMillis()); " + " log.info("run end-------------createTime="+object.get("createTime")); " + " log.info("run end-------------orderNO="+orderNO); " + " return object.toJSONString(); " + " } " + "}"; // 第一次执行 method(code); System.out.println("-------------------"); // 第二次执行 method(code); } private void method(String code) { CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(code); boolean res = compiler.compiler(); if (res) { try { JSONObject object = new JSONObject(); object.put("productNO", "P001203"); object.put("orderNo", "001203"); String result = compiler.runMainMethod(object.toJSONString()); System.out.println("-----------------result=" + result); } catch (Exception e) { e.printStackTrace(); } System.out.println("诊断信息:" + compiler.getCompilerMessage()); } else { System.out.println("编译失败"); System.out.println(compiler.getCompilerMessage()); } }
执行结果:
4.注意
如果报错找不到:
java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment
需要引入tools.jar包