Springboot动态生成一个项目中没有的类(class对象)
这两天新接到一个需求,是这样。从页面上文本写一个拦截器,然后上传这个拦截器的源码,生成对象并调用对象的方法。
我当时的反应就是很懵逼的 。。。这个操作也太骚了吧
年前写了个用groovy来执行,但是会出现一些问题,不能满足需求。
年后开始重新思考这个问题,然后在网上找到了一篇资料
http://blog.sina.com.cn/s/blog_70279be20101dk0j.html

重点是找到了一个东西
jdk提供一个动态编译的类。JavaCompiler javac;
这样首先是解决了,将源码编译成为对应的class文件,接下来是动态加载class文件到项目中生成。这一步就通过类加载器来完成。
理清除思路后,我就开始做一个最简单的通过main方法执行生成一个动态类 ,然后将代码移植到springboot项目中。
import javax.tools.JavaCompiler;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import java.io.IOException;import java.util.Arrays;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;/*** @author: Bobby* @create: 2020-02-11 15:29* @description:动态执行**/public class DynamicLoader {/*** auto fill in the java-name with code, return null if cannot find the public class** @param javaSrc source code string* @return return the Map, the KEY means ClassName, the VALUE means bytecode.*/public static Map<String, byte[]> compile(String javaSrc) {Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");Matcher matcher = pattern.matcher(javaSrc);if (matcher.find()) {return compile(matcher.group(1) + ".java", javaSrc);}return null;}public static Map<String, byte[]> compile(String javaName, String javaSrc) {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));if (task.call())return manager.getClassBytes();} catch (IOException e) {e.printStackTrace();}return null;}}
import javax.tools.*;import java.io.*;import java.net.URI;import java.net.URL;import java.net.URLClassLoader;import java.nio.CharBuffer;import java.util.HashMap;import java.util.Map;/*** @author: Bobby* @create: 2020-02-11 16:10* @description:**/public class MemoryClassLoader extends URLClassLoader {Map<String, byte[]> classBytes = new HashMap<String, byte[]>();public MemoryClassLoader(Map<String, byte[]> classBytes) {super(new URL[0], MemoryClassLoader.class.getClassLoader());this.classBytes.putAll(classBytes);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] buf = classBytes.get(name);if (buf == null) {return super.findClass(name);}classBytes.remove(name);return defineClass(name, buf, 0, buf.length);}}/*** * MemoryJavaFileManager.java* JavaFileManager that keeps compiled .class bytes in memory.*/@SuppressWarnings("unchecked")final class MemoryJavaFileManager extends ForwardingJavaFileManager {/*** Java source file extension.*/private final static String EXT = ".java";private Map<String, byte[]> classBytes;public MemoryJavaFileManager(JavaFileManager fileManager) {super(fileManager);classBytes = new HashMap<String, byte[]>();}public Map<String, byte[]> getClassBytes() {return classBytes;}public void close() throws IOException {classBytes = new HashMap<String, byte[]>();}public void flush() throws IOException {}/*** A file object used to represent Java source coming from a string.*/private static class StringInputBuffer extends SimpleJavaFileObject {final String code;StringInputBuffer(String name, String code) {super(toURI(name), Kind.SOURCE);this.code = code;}public CharBuffer getCharContent(boolean ignoreEncodingErrors) {return CharBuffer.wrap(code);}public Reader openReader() {return new StringReader(code);}}/*** A file object that stores Java bytecode into the classBytes map.*/private class ClassOutputBuffer extends SimpleJavaFileObject {private String name;ClassOutputBuffer(String name) {super(toURI(name), Kind.CLASS);this.name = name;}public OutputStream openOutputStream() {return new FilterOutputStream(new ByteArrayOutputStream()) {public void close() throws IOException {out.close();ByteArrayOutputStream bos = (ByteArrayOutputStream) out;classBytes.put(name, bos.toByteArray());}};}}public JavaFileObject getJavaFileForOutput(Location location,String className,JavaFileObject.Kind kind,FileObject sibling) throws IOException {if (kind == JavaFileObject.Kind.CLASS) {return new ClassOutputBuffer(className);} else {return super.getJavaFileForOutput(location, className, kind, sibling);}}static JavaFileObject makeStringSource(String name, String code) {return new StringInputBuffer(name, code);}static URI toURI(String name) {File file = new File(name);if (file.exists()) {return file.toURI();} else {try {final StringBuilder newUri = new StringBuilder();newUri.append("mfm:///");newUri.append(name.replace('.', '/'));if (name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);return URI.create(newUri.toString());} catch (Exception exp) {return URI.create("mfm:///com/sun/script/java/java_source");}}}}
然后开始通过接口测试
import com.ocft.gateway.clazzloader.DynamicLoader;import com.ocft.gateway.clazzloader.MemoryClassLoader;import com.ocft.gateway.common.context.GatewayContext;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Iterator;import java.util.Map;import java.util.Random;/*** @author: Bobby* @create: 2020-02-14 20:28* @description:**/@RestController@RequestMapping("/dynamicloader")public class DynamicLoaderTestController {private static String javaSrc = "public class TestClass{" +"public void sayHello(String msg) {" +"System.out.printf(\"Hello %s! This message from a Java String.%n\",msg);" +"}" +"public int add(int a,int b){" +"return a+b;" +"}" +"}";private static String javaSrcInterreptor="import com.alibaba.fastjson.JSONObject;\n" +"import com.ocft.gateway.common.context.GatewayContext;\n" +"import com.ocft.gateway.common.evaluator.JsonOperateEvalutor;\n" +"import com.ocft.gateway.common.exceptions.GatewayException;\n" +"import com.ocft.gateway.entity.GatewayInterface;\n" +"import com.ocft.gateway.entity.InterfaceConfig;\n" +"import com.ocft.gateway.entity.RequestAccessLimit;\n" +"import com.ocft.gateway.entity.RequestType;\n" +"import com.ocft.gateway.interceptor.GatewayInterceptor;\n" +"import com.ocft.gateway.service.IInterfaceConfigService;\n" +"import com.ocft.gateway.service.IRequestTypeService;\n" +"import com.ocft.gateway.utils.MathUtil;\n" +"import com.ocft.gateway.utils.RedisUtil;\n" +"import com.ocft.gateway.utils.WebUtil;\n" +"import lombok.extern.slf4j.Slf4j;\n" +"import org.apache.commons.lang3.StringUtils;\n" +"import org.slf4j.Logger;\n" +"import org.slf4j.LoggerFactory;\n" +"import org.springframework.beans.factory.annotation.Autowired;\n" +"import org.springframework.stereotype.Component;\n" +"import javax.servlet.http.HttpServletRequest;\n" +"import java.util.Date;\n" +"import java.util.List;" +"@Slf4j\n" +"@Component\n" +"public class TestIntercept implements GatewayInterceptor {" +" @Override\n" +" public void doInterceptor(GatewayContext context) {" +" System.out.printf(\"Hello Bobby\"); " +" }" +"}" +"";@RequestMapping("/test")public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {Map<String, byte[]> bytecode = DynamicLoader.compile("TestIntercept.java", javaSrcInterreptor);MemoryClassLoader classLoader = new MemoryClassLoader(bytecode);Class clazz = classLoader.loadClass("TestIntercept");Object object = clazz.newInstance();Method doInterceptor = clazz.getMethod("doInterceptor", GatewayContext.class);doInterceptor.invoke(object, new GatewayContext());}public void testCompile() {Map<String, byte[]> bytecode = DynamicLoader.compile("TestClass.java", javaSrc);for (Iterator<String> iterator = bytecode.keySet().iterator(); iterator.hasNext(); ) {String key = iterator.next();byte[] code = bytecode.get(key);System.out.printf("Class: %s, Length: %d%n", key, code.length);}}
最后可以成功输出

赞 (0)
