Springboot动态生成一个项目中没有的类(class对象)

这两天新接到一个需求,是这样。从页面上文本写一个拦截器,然后上传这个拦截器的源码,生成对象并调用对象的方法。

我当时的反应就是很懵逼的 。。。这个操作也太骚了吧

年前写了个用groovy来执行,但是会出现一些问题,不能满足需求。

年后开始重新思考这个问题,然后在网上找到了一篇资料

http://blog.sina.com.cn/s/blog_70279be20101dk0j.html

重点是找到了一个东西

jdk提供一个动态编译的类。JavaCompiler javac;

这样首先是解决了,将源码编译成为对应的class文件,接下来是动态加载class文件到项目中生成。这一步就通过类加载器来完成。

理清除思路后,我就开始做一个最简单的通过main方法执行生成一个动态类 ,然后将代码移植到springboot项目中。

  1. import javax.tools.JavaCompiler;
  2. import javax.tools.JavaFileObject;
  3. import javax.tools.StandardJavaFileManager;
  4. import javax.tools.ToolProvider;
  5. import java.io.IOException;
  6. import java.util.Arrays;
  7. import java.util.Map;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;

  10. /**
  11. * @author: Bobby
  12. * @create: 2020-02-11 15:29
  13. * @description:动态执行
  14. **/
  15. public class DynamicLoader {

  16. /**
  17. * auto fill in the java-name with code, return null if cannot find the public class
  18. *
  19. * @param javaSrc source code string
  20. * @return return the Map, the KEY means ClassName, the VALUE means bytecode.
  21. */
  22. public static Map<String, byte[]> compile(String javaSrc) {
  23. Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
  24. Matcher matcher = pattern.matcher(javaSrc);
  25. if (matcher.find()) {
  26. return compile(matcher.group(1) + ".java", javaSrc);
  27. }
  28. return null;
  29. }

  30. public static Map<String, byte[]> compile(String javaName, String javaSrc) {
  31. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  32. StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
  33. try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
  34. JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);
  35. JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
  36. if (task.call())
  37. return manager.getClassBytes();
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. return null;

  42. }


  43. }
  1. import javax.tools.*;
  2. import java.io.*;
  3. import java.net.URI;
  4. import java.net.URL;
  5. import java.net.URLClassLoader;
  6. import java.nio.CharBuffer;
  7. import java.util.HashMap;
  8. import java.util.Map;

  9. /**
  10. * @author: Bobby
  11. * @create: 2020-02-11 16:10
  12. * @description:
  13. **/
  14. public class MemoryClassLoader extends URLClassLoader {
  15. Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

  16. public MemoryClassLoader(Map<String, byte[]> classBytes) {
  17. super(new URL[0], MemoryClassLoader.class.getClassLoader());
  18. this.classBytes.putAll(classBytes);
  19. }

  20. @Override
  21. protected Class<?> findClass(String name) throws ClassNotFoundException {
  22. byte[] buf = classBytes.get(name);
  23. if (buf == null) {
  24. return super.findClass(name);
  25. }
  26. classBytes.remove(name);
  27. return defineClass(name, buf, 0, buf.length);
  28. }

  29. }


  30. /**
  31. * * MemoryJavaFileManager.java
  32. * JavaFileManager that keeps compiled .class bytes in memory.
  33. */
  34. @SuppressWarnings("unchecked")
  35. final class MemoryJavaFileManager extends ForwardingJavaFileManager {
  36. /**
  37. * Java source file extension.
  38. */
  39. private final static String EXT = ".java";
  40. private Map<String, byte[]> classBytes;

  41. public MemoryJavaFileManager(JavaFileManager fileManager) {
  42. super(fileManager);
  43. classBytes = new HashMap<String, byte[]>();
  44. }

  45. public Map<String, byte[]> getClassBytes() {
  46. return classBytes;
  47. }

  48. public void close() throws IOException {
  49. classBytes = new HashMap<String, byte[]>();
  50. }

  51. public void flush() throws IOException {
  52. }

  53. /**
  54. * A file object used to represent Java source coming from a string.
  55. */
  56. private static class StringInputBuffer extends SimpleJavaFileObject {
  57. final String code;

  58. StringInputBuffer(String name, String code) {
  59. super(toURI(name), Kind.SOURCE);
  60. this.code = code;
  61. }

  62. public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
  63. return CharBuffer.wrap(code);
  64. }

  65. public Reader openReader() {
  66. return new StringReader(code);
  67. }
  68. }

  69. /**
  70. * A file object that stores Java bytecode into the classBytes map.
  71. */
  72. private class ClassOutputBuffer extends SimpleJavaFileObject {
  73. private String name;

  74. ClassOutputBuffer(String name) {
  75. super(toURI(name), Kind.CLASS);
  76. this.name = name;
  77. }

  78. public OutputStream openOutputStream() {
  79. return new FilterOutputStream(new ByteArrayOutputStream()) {
  80. public void close() throws IOException {
  81. out.close();
  82. ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
  83. classBytes.put(name, bos.toByteArray());
  84. }
  85. };
  86. }
  87. }

  88. public JavaFileObject getJavaFileForOutput(Location location,
  89. String className,
  90. JavaFileObject.Kind kind,
  91. FileObject sibling) throws IOException {
  92. if (kind == JavaFileObject.Kind.CLASS) {
  93. return new ClassOutputBuffer(className);
  94. } else {
  95. return super.getJavaFileForOutput(location, className, kind, sibling);
  96. }
  97. }

  98. static JavaFileObject makeStringSource(String name, String code) {
  99. return new StringInputBuffer(name, code);
  100. }

  101. static URI toURI(String name) {
  102. File file = new File(name);
  103. if (file.exists()) {
  104. return file.toURI();
  105. } else {
  106. try {
  107. final StringBuilder newUri = new StringBuilder();
  108. newUri.append("mfm:///");
  109. newUri.append(name.replace('.', '/'));
  110. if (name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
  111. return URI.create(newUri.toString());
  112. } catch (Exception exp) {
  113. return URI.create("mfm:///com/sun/script/java/java_source");
  114. }
  115. }
  116. }
  117. }

然后开始通过接口测试

 

 

  1. import com.ocft.gateway.clazzloader.DynamicLoader;
  2. import com.ocft.gateway.clazzloader.MemoryClassLoader;
  3. import com.ocft.gateway.common.context.GatewayContext;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;

  6. import java.lang.reflect.InvocationTargetException;
  7. import java.lang.reflect.Method;
  8. import java.util.Iterator;
  9. import java.util.Map;
  10. import java.util.Random;

  11. /**
  12. * @author: Bobby
  13. * @create: 2020-02-14 20:28
  14. * @description:
  15. **/
  16. @RestController
  17. @RequestMapping("/dynamicloader")
  18. public class DynamicLoaderTestController {


  19. private static String javaSrc = "public class TestClass{" +
  20. "public void sayHello(String msg) {" +
  21. "System.out.printf(\"Hello %s! This message from a Java String.%n\",msg);" +
  22. "}" +
  23. "public int add(int a,int b){" +
  24. "return a+b;" +
  25. "}" +
  26. "}";


  27. private static String javaSrcInterreptor=
  28. "import com.alibaba.fastjson.JSONObject;\n" +
  29. "import com.ocft.gateway.common.context.GatewayContext;\n" +
  30. "import com.ocft.gateway.common.evaluator.JsonOperateEvalutor;\n" +
  31. "import com.ocft.gateway.common.exceptions.GatewayException;\n" +
  32. "import com.ocft.gateway.entity.GatewayInterface;\n" +
  33. "import com.ocft.gateway.entity.InterfaceConfig;\n" +
  34. "import com.ocft.gateway.entity.RequestAccessLimit;\n" +
  35. "import com.ocft.gateway.entity.RequestType;\n" +
  36. "import com.ocft.gateway.interceptor.GatewayInterceptor;\n" +
  37. "import com.ocft.gateway.service.IInterfaceConfigService;\n" +
  38. "import com.ocft.gateway.service.IRequestTypeService;\n" +
  39. "import com.ocft.gateway.utils.MathUtil;\n" +
  40. "import com.ocft.gateway.utils.RedisUtil;\n" +
  41. "import com.ocft.gateway.utils.WebUtil;\n" +
  42. "import lombok.extern.slf4j.Slf4j;\n" +
  43. "import org.apache.commons.lang3.StringUtils;\n" +
  44. "import org.slf4j.Logger;\n" +
  45. "import org.slf4j.LoggerFactory;\n" +
  46. "import org.springframework.beans.factory.annotation.Autowired;\n" +
  47. "import org.springframework.stereotype.Component;\n" +
  48. "import javax.servlet.http.HttpServletRequest;\n" +
  49. "import java.util.Date;\n" +
  50. "import java.util.List;" +
  51. "@Slf4j\n" +
  52. "@Component\n" +
  53. "public class TestIntercept implements GatewayInterceptor {" +
  54. " @Override\n" +
  55. " public void doInterceptor(GatewayContext context) {" +
  56. " System.out.printf(\"Hello Bobby\"); " +
  57. " }" +
  58. "}" +
  59. "";

  60. @RequestMapping("/test")
  61. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
  62. Map<String, byte[]> bytecode = DynamicLoader.compile("TestIntercept.java", javaSrcInterreptor);
  63. MemoryClassLoader classLoader = new MemoryClassLoader(bytecode);
  64. Class clazz = classLoader.loadClass("TestIntercept");
  65. Object object = clazz.newInstance();
  66. Method doInterceptor = clazz.getMethod("doInterceptor", GatewayContext.class);
  67. doInterceptor.invoke(object, new GatewayContext());
  68. }



  69. public void testCompile() {
  70. Map<String, byte[]> bytecode = DynamicLoader.compile("TestClass.java", javaSrc);
  71. for (Iterator<String> iterator = bytecode.keySet().iterator(); iterator.hasNext(); ) {
  72. String key = iterator.next();
  73. byte[] code = bytecode.get(key);
  74. System.out.printf("Class: %s, Length: %d%n", key, code.length);
  75. }
  76. }

最后可以成功输出  

(0)

相关推荐