代碼來源於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;\n" + "\n" + "public class UserService {\n" + "\n" + " private IndexService service;\n" + "\n" + " public void user(){\n" + " service.query();\n" + " }\n" + "\n" + " public void setService(IndexService service){\n" + " this.service = service;\n" + " }\n" + "\n" + "}"; 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包解壓的操作