java代碼實(shí)現(xiàn)編譯源文件
原理
從Java 6開始,引入了Java代碼重寫過的編譯器接口,使得我們可以在運(yùn)行時(shí)編譯Java源代碼,然后再通過類加載器將編譯好的類加載進(jìn)JVM,這種在運(yùn)行時(shí)編譯代碼的操作就叫做動(dòng)態(tài)編譯。
主要類庫
JavaCompiler -表示java編譯器, run方法執(zhí)行編譯操作. 還有一種編譯方式是先生成編譯任務(wù)(CompilationTask), 讓后調(diào)用CompilationTask的call方法執(zhí)行編譯任務(wù)
JavaFileObject -表示一個(gè)java源文件對象
JavaFileManager - Java源文件管理類, 管理一系列JavaFileObject
Diagnostic -表示一個(gè)診斷信息
DiagnosticListener -診斷信息監(jiān)聽器, 編譯過程觸發(fā). 生成編譯task(JavaCompiler#getTask())或獲取FileManager(JavaCompiler#getStandardFileManager())時(shí)需要傳遞DiagnosticListener以便收集診斷信息
流程圖
源碼文件 -> 字節(jié)碼文件
public static void fromJavaFile() { // 獲取Javac編譯器對象 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 獲取文件管理器:負(fù)責(zé)管理類文件的輸入輸出 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 獲取要被編譯的Java源文件 File file = new File("/Users//github/perfect/perfect-javassist/src/main/java/com/jc/javassist/compiler/TestHello.java"); // 通過源文件獲取到要編譯的Java類源碼迭代器,包括所有內(nèi)部類,其中每個(gè)類都是一個(gè)JavaFileObject Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file); // 生成編譯任務(wù) JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits); // 執(zhí)行編譯任務(wù) task.call(); }
我們這里準(zhǔn)備了TestHello.java
public class TestHello { public static void main(String[] args) { System.out.println("this is a test"); } }
我們試著手動(dòng)加載該class文件,使用類加載器的defineClass方法,可以直接加載字節(jié)碼文件。
public static Class<?> loadClassFromDisk(String path) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // defineClass 為 ClassLoader 類的一個(gè)方法,用于加載類 // 但是這個(gè)方法是 protected 的,所以需要通過反射的方式獲取這個(gè)方法的權(quán)限 Class<ClassLoader> classLoaderClass = ClassLoader.class; Method defineClass = classLoaderClass.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); defineClass.setAccessible(true); // 讀取文件系統(tǒng)的 file 為 byte 數(shù)組 File file = new File(path); byte[] bytes = new byte[(int) file.length()]; try (FileInputStream fileInputStream = new FileInputStream(file)) { fileInputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } // 執(zhí)行 defineClass 方法 返回 Class 對象 return (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length); }
由于TestHello中的方法為靜態(tài)方法,使用class反射機(jī)制執(zhí)行方法
// 執(zhí)行編譯任務(wù) Boolean call = task.call(); if (call) { Class<?> o = loadClassFromDisk("/Users/oneyoung/oneyoung/project/my/code/src/main/java/top/oneyoung/dynamic/TestHello.class"); Method main = o.getMethod("main", String[].class); main.invoke(null, new Object[]{new String[]{}}); }
執(zhí)行結(jié)果
| this is a test
源碼字符串 -> 字節(jié)碼文件
在流程圖中,getTask().call()會通過調(diào)用作為參數(shù)傳入的JavaFileObject對象的getCharContent()方法獲得字符串序列,即源碼的讀取是通過 JavaFileObject的 getCharContent()方法,那我們只需要重寫getCharContent()方法,即可將我們的字符串源碼裝進(jìn)JavaFileObject了。構(gòu)造SourceJavaFileObject實(shí)現(xiàn)定制的JavaFileObject對象,用于存儲字符串源碼
public class SourceJavaFileObject extends SimpleJavaFileObject { /** * The source code of this "file". */ private final String code; SourceJavaFileObject(String name, String code) { super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } }
則創(chuàng)建JavaFileObject對象時(shí),變?yōu)榱耍?/p>
// 通過源代碼字符串獲取到要編譯的Java類源碼迭代器,包括所有內(nèi)部類,其中每個(gè)類都是一個(gè)JavaFileObject SourceJavaFileObject javaFileObject = new SourceJavaFileObject("TestHello", "public class TestHello { public static void main(String[] args) { System.out.println("Hello World"); } }"); // 生成編譯任務(wù) JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, Collections.singleton(javaFileObject));
執(zhí)行后,同樣編譯出了class文件,不過由于沒有指定編譯的class輸出路徑,他會默認(rèn)放在源文件的根目錄下
源碼字符串 -> 字節(jié)碼數(shù)組
如果我們進(jìn)行動(dòng)態(tài)編譯時(shí),想要直接輸入源碼字符串并且輸出的是字節(jié)碼數(shù)組,而不是輸出字節(jié)碼文件,又該如何實(shí)現(xiàn)?實(shí)際上,這是從內(nèi)存中得到源碼,再輸出到內(nèi)存的方式。
在getTask().call()源代碼執(zhí)行流程圖中,我們可以發(fā)現(xiàn)JavaFileObject 的 openOutputStream()方法控制了編譯后字節(jié)碼的輸出行為,編譯完成后會調(diào)用openOutputStream獲取輸出流,并寫數(shù)據(jù)(字節(jié)碼)。所以我們需要重寫JavaFileObject 的 openOutputStream()方法。
同時(shí)在執(zhí)行流程圖中,我們還發(fā)現(xiàn)用于輸出的JavaFileObject 對象是JavaFileManager的getJavaFileForOutput()方法提供的,所以為了讓編譯器編譯完成后,將編譯得到的字節(jié)碼輸出到我們自己構(gòu)造的JavaFileObject 對象,我們還需要自定義JavaFileManager。
這里我使用類委托的方式,把大部分功能委托給了傳入的StandardJavaFileManager,主要是重寫了getJavaFileForOutput,使輸出編譯完成的字節(jié)碼文件為字節(jié)數(shù)組。
然后增加了方法getBytesByClassName獲取編譯完成的字節(jié)碼字節(jié)數(shù)組
public class ByteArrayJavaFileManager implements JavaFileManager { private static final Logger LOG = LoggerFactory.getLogger(ByteArrayJavaFileManager.class); private final StandardJavaFileManager fileManager; /** * synchronizing due to ConcurrentModificationException */ private final Map<String, ByteArrayOutputStream> buffers = Collections.synchronizedMap(new LinkedHashMap<>()); public ByteArrayJavaFileManager(StandardJavaFileManager fileManager) { this.fileManager = fileManager; } @Override public ClassLoader getClassLoader(Location location) { return fileManager.getClassLoader(location); } @Override public synchronized Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { return fileManager.list(location, packageName, kinds, recurse); } @Override public String inferBinaryName(Location location, JavaFileObject file) { return fileManager.inferBinaryName(location, file); } @Override public boolean isSameFile(FileObject a, FileObject b) { return fileManager.isSameFile(a, b); } @Override public synchronized boolean handleOption(String current, Iterator<String> remaining) { return fileManager.handleOption(current, remaining); } @Override public boolean hasLocation(Location location) { return fileManager.hasLocation(location); } @Override public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { if (location == StandardLocation.CLASS_OUTPUT) { boolean success; final byte[] bytes; synchronized (buffers) { success = buffers.containsKey(className) && kind == Kind.CLASS; bytes = buffers.get(className).toByteArray(); } if (success) { return new SimpleJavaFileObject(URI.create(className), kind) { @Override public InputStream openInputStream() { return new ByteArrayInputStream(bytes); } }; } } return fileManager.getJavaFileForInput(location, className, kind); } @Override public JavaFileObject getJavaFileForOutput(Location location, final String className, Kind kind, FileObject sibling) { return new SimpleJavaFileObject(URI.create(className), kind) { @Override public OutputStream openOutputStream() { // 字節(jié)輸出流用與FileManager輸出編譯完成的字節(jié)碼文件為字節(jié)數(shù)組 ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 將每個(gè)需要加載的類的輸出流進(jìn)行緩存 buffers.putIfAbsent(className, bos); return bos; } }; } @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { return fileManager.getFileForInput(location, packageName, relativeName); } @Override public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { return fileManager.getFileForOutput(location, packageName, relativeName, sibling); } @Override public void flush() { // Do nothing } @Override public void close() throws IOException { fileManager.close(); } @Override public int isSupportedOption(String option) { return fileManager.isSupportedOption(option); } public void clearBuffers() { buffers.clear(); } public Map<String, byte[]> getAllBuffers() { Map<String, byte[]> ret = new LinkedHashMap<>(buffers.size() * 2); Map<String, ByteArrayOutputStream> compiledClasses = new LinkedHashMap<>(ret.size()); synchronized (buffers) { compiledClasses.putAll(buffers); } compiledClasses.forEach((k, v) -> ret.put(k, v.toByteArray())); return ret; } public byte[] getBytesByClassName(String className) { return buffers.get(className).toByteArray(); } }
然后我們修改下之前的執(zhí)行流程
public static void fromJavaSourceToByteArray1() throws Exception { // 獲取Javac編譯器對象 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 獲取文件管理器:負(fù)責(zé)管理類文件的輸入輸出 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 創(chuàng)建自定義的FileManager ByteArrayJavaFileManager byteArrayJavaFileManager = new ByteArrayJavaFileManager(fileManager); // 通過源代碼字符串獲取到要編譯的Java類源碼迭代器,包括所有內(nèi)部類,其中每個(gè)類都是一個(gè)JavaFileObject JavaFileObject javaFileObject = new SourceJavaFileObject("TestHello", "public class TestHello { public static void say(String args) { System.out.println(args); } }"); JavaCompiler.CompilationTask task = compiler.getTask(null, byteArrayJavaFileManager, null, null, null, Collections.singletonList(javaFileObject)); // 執(zhí)行編譯任務(wù) Boolean call = task.call(); if (Boolean.TRUE.equals(call)) { byte[] testHellos = byteArrayJavaFileManager.getBytesByClassName("TestHello"); Class<ClassLoader> classLoaderClass = ClassLoader.class; Method defineClass = classLoaderClass.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); defineClass.setAccessible(true); Object invoke = defineClass.invoke(TestHello.class.getClassLoader(), testHellos, 0, testHellos.length); Class clazz = (Class) invoke; clazz.getMethod("say", String.class).invoke(null, "你好"); } }
以上就是java代碼實(shí)現(xiàn)編譯源文件的詳細(xì)內(nèi)容,更多關(guān)于java編譯源文件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Spring Data JPA中Repository的接口查詢方法
repository代理有兩種方式從方法名中派生出特定存儲查詢:通過直接從方法名派生查詢和通過使用一個(gè)手動(dòng)定義的查詢。本文將通過示例詳細(xì)講解Spring Data JPA中Repository的接口查詢方法,需要的可以參考一下2022-04-04spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法
這篇文章主要介紹了spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05java8 stream的多字段排序?qū)崿F(xiàn)(踩坑)
這篇文章主要介紹了java8 stream的多字段排序?qū)崿F(xiàn)(踩坑),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Java正則表達(dá)式學(xué)習(xí)之分組與替換
這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式學(xué)習(xí)之分組與替換的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09SpringMVC框架實(shí)現(xiàn)Handler處理器的三種寫法
這篇文章主要介紹了SpringMVC框架實(shí)現(xiàn)Handler處理器的三種寫法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02@JsonFormat 實(shí)現(xiàn)日期格式自動(dòng)格式化
這篇文章主要介紹了@JsonFormat 實(shí)現(xiàn)日期格式自動(dòng)格式化,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot生成Excel文件的實(shí)現(xiàn)示例
本文介紹了Spring Boot項(xiàng)目中生成Excel文件,使用了Apache POI庫,包括poi和poi-ooxml依賴,通過遍歷用戶信息列表,將數(shù)據(jù)寫入Excel文件,感興趣的可以了解一下2025-02-02