代码来源于https://github.com/hxulin/dynamic-compile-samples.git
引入编译包
<dependency> <groupId>com.itranswarp</groupId> <artifactId>compiler</artifactId> <version>1.0</version> </dependency>
添加被调用的类
package com.example.demo.dynamic; public class IndexService { public void query(){ System.out.println("query"); } }
添加测试类
package com.example.demo.dynamic; import com.itranswarp.compiler.JavaStringCompiler; import java.lang.reflect.Method; import java.util.Map; public class DynamicTest { public static final String code = "package com.example.demo.dynamic; " + " " + "public class UserService { " + " " + " private IndexService service; " + " " + " public void user(){ " + " service.query(); " + " } " + " " + " public void setService(IndexService service){ " + " this.service = service; " + " } " + " " + "}"; public static void main(String[] args) throws Exception { JavaStringCompiler compiler = new JavaStringCompiler(); Map<String, byte[]> compile = compiler.compile("UserService.java", code); Class<?> aClass = compiler.loadClass("com.example.demo.dynamic.UserService", compile); Method setService = aClass.getMethod("setService", IndexService.class); Object o = aClass.newInstance(); setService.invoke(o,new IndexService()); Method user = aClass.getMethod("user"); user.invoke(o); } }
使用jdk自带的比较复杂,所以使用已有的编译包
补充,在后续的测试中,将测试代码添加到一个SpringBoot项目中,在idea中通过main方法启动项目,暴露接口传入java代码,可以编译,但是将springboot打包成jar启动后,传入java代码,编译失败,找不到符号
目前有一种方法,已经测试成功可以运行,仅供测试用哈
1、创建一个springboot项目,添加上面的编译依赖,
项目主要用到的类
2、dy包中的类
package com.example.demo.dy; public class MyClassLoad extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { MyJavaFileObject javaFileObject = MyJavaFileManager.fileObjects.get(name); if(javaFileObject != null){ byte[] compileByte = javaFileObject.getCompileByte(); return defineClass(name,compileByte,0,compileByte.length); } try { return ClassLoader.getSystemClassLoader().loadClass(name); }catch (Exception e){ return super.findClass(name); } } }
package com.example.demo.dy; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; public class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> { public static Map<String, MyJavaFileObject> fileObjects = new ConcurrentHashMap<>(); public MyJavaFileManager(JavaFileManager fileManager) { super(fileManager); } @Override public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { JavaFileObject javaFileObject = fileObjects.get(className); if (javaFileObject == null){ super.getJavaFileForInput(location,className,kind); } return javaFileObject; } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { MyJavaFileObject javaFileObject = new MyJavaFileObject(className,kind); fileObjects.put(className,javaFileObject); return javaFileObject; } }
package com.example.demo.dy; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import javax.tools.SimpleJavaFileObject; public class MyJavaFileObject extends SimpleJavaFileObject { private String source; private ByteArrayOutputStream outputStream; public MyJavaFileObject(String name,String source) { super(URI.create("String:///"+name),Kind.SOURCE); this.source = source; } public MyJavaFileObject(String name,Kind kind){ super(URI.create("String:///"+name),kind); source = null; } @Override public OutputStream openOutputStream() throws IOException { outputStream = new ByteArrayOutputStream(); return outputStream; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { if(source == null){ throw new IllegalArgumentException("source == null"); } return source; } public byte[] getCompileByte(){ return outputStream.toByteArray(); } }
3、controller包中的类(SendController没用)
package com.example.demo.controller; public interface UserService { public String user(); }
package com.example.demo.controller; import org.springframework.stereotype.Service; @Service public class IndexService { public String index() { return "indexService"; } }
package com.example.demo.controller; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.example.demo.dy.MyClassLoad; import com.example.demo.dy.MyJavaFileManager; import com.example.demo.dy.MyJavaFileObject; import com.itranswarp.compiler.JavaStringCompiler; @RestController public class IndexController { @Autowired private IndexService indexService; @GetMapping("index") public String index() { return "Success"; } @PostMapping("compile") public String compile(@RequestBody String message) { JavaStringCompiler compiler = new JavaStringCompiler(); try { Map<String, byte[]> compile = compiler.compile("UserServiceImpl.java", message,null); Class<?> loadClass = compiler.loadClass("com.example.demo.service.UserServiceImpl",compile); Object userService = loadClass.newInstance(); Method method = loadClass.getMethod("setIndexService", IndexService.class); method.invoke(userService, indexService); if(userService instanceof UserService) { UserService service = (UserService)userService; return service.user(); } return null; } catch (Exception e) { e.printStackTrace(); return null; }finally { } } @PostMapping("compile1") public String compile1(@RequestBody String message) throws IOException { JavaStringCompiler compiler = new JavaStringCompiler(); List<String> options = new ArrayList<>(); options.add("-classpath"); options.add("./BOOT-INF/classes/"); try { Map<String, byte[]> compile = compiler.compile("UserServiceImpl.java", message,options); Class<?> loadClass = compiler.loadClass("com.example.demo.controller.UserServiceImpl",compile); Object userService = loadClass.newInstance(); Method method = loadClass.getMethod("setIndexService", IndexService.class); method.invoke(userService, indexService); if(userService instanceof UserService) { UserService service = (UserService)userService; return service.user(); } return null; } catch (Exception e) { e.printStackTrace(); return null; }finally { } } @PostMapping("compile3") public String compile3(@RequestBody String message) { JavaStringCompiler compiler = new JavaStringCompiler(); List<String> options = new ArrayList<>(); String classpath = this.getClass().getClassLoader().getResource("").getPath(); options.add("-cp"); options.add(classpath); try { Map<String, byte[]> compile = compiler.compile("HelloWorld.java", message,options); Class<?> loadClass = compiler.loadClass("HelloWorld",compile); Method print = loadClass.getMethod("print"); print.invoke(loadClass.newInstance()); } catch (Exception e) { e.printStackTrace(); return null; }finally { } return null; } @PostMapping("compile2") public String compile2(@RequestBody String message) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); JavaFileManager fileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector,null,null)); String current = this.getClass().getResource("").getPath(); String classpath = this.getClass().getClassLoader().getResource("").getPath(); List<String> options = new ArrayList<>(); // options.add("-target"); // options.add("1.8"); System.out.println(classpath); System.out.println(current); options.add("-d"); options.add("."); options.add("-cp"); options.add(classpath); JavaFileObject javaFileObject = new MyJavaFileObject("UserServiceImpl.java",message); Boolean call = compiler.getTask(null, fileManager, collector, options, null, Arrays.asList(javaFileObject)).call(); if(!call) { return "fail"; } ClassLoader classLoader = new MyClassLoad(); Class<?> loadClass = null; try { loadClass = classLoader.loadClass("com.example.demo.service.UserServiceImpl"); Object userService = loadClass.newInstance(); Method method = loadClass.getMethod("setIndexService", IndexService.class); method.invoke(userService, indexService); if(userService instanceof UserService) { UserService service = (UserService)userService; return service.user(); } } catch (Exception e) { e.printStackTrace(); } return null; } }
4、之后将项目打包,放到一个目录中,然后解压到当前目录下
为啥要解压呢,主要是为了方便5中指定classpath(主要解决编译找不到符号的问题)
目录结构
5、测试接口--主要测试compile1,这个接口中在编译的时候添加了-classpath的参数,这样的话,动态编译的时候能够找到符号引用
使用postman测试
package com.example.demo.controller; import com.example.demo.controller.UserService; import com.example.demo.controller.IndexService; public class UserServiceImpl implements UserService { private IndexService indexService; public void setIndexService(IndexService indexService) { this.indexService = indexService; } @Override public String user() { return indexService.index(); } }
完,目前还不知道有啥问题,待后续补充吧
6、还有一个
就是上面的依赖的编译包,编译方法添加了个参数。
补充:
对compile2接口做调整
添加了一个父类类加载器,不然在加载类的时候,类中关于其他类的引用加载不到
补充:查看类能否被卸载
添加gc方法调用
虚拟机参数添加:
-XX:+TraceClassUnloading
然后查看日志打印,应该可有看到类的卸载信息
补充:
为了方便jar包部署,添加了一个在jar包启动完成后,进行jar包解压的操作