Java用Java编译
在上一篇文章中,我寫了關于如何在運行時生成代理的信息,我們已經了解到生成Java源代碼的程度。 但是,要使用該類,必須對其進行編譯,并將生成的字節碼加載到內存中。 那是“編譯”時間。 幸運的是,從Java 1.6開始,我們可以在運行時訪問Java編譯器,因此可以將編譯時與運行時混淆。 盡管在這種非常特殊的情況下,盡管這可能會導致太多麻煩的事情,通常會導致無法維護的自我修改代碼,但它可能還是有用的:我們可以編譯運行時生成的代理。
Java編譯器API
Java編譯器讀取源文件并生成類文件。 (將它們組裝到JAR,WAR,EAR和其他軟件包中是另一種工具的責任。)源文件和類文件不一定是駐留在磁盤,SSD或內存驅動器中的真實操作系統文件。 畢竟,當涉及到運行時API時,Java通常對于抽象是很好的,現在就是這種情況。 這些文件是一些“抽象”文件,您必須通過API提供訪問這些文件,這些文件可以是磁盤文件,但同時幾乎可以是任何其他文件。 將源代碼保存到磁盤上只是為了讓編譯器在相同的進程中運行以將其讀回并在類文件準備就緒時對其進行相同的處理,通常會浪費資源。
Java編譯器作為運行時可用的API,要求您提供一些簡單的API(或您所稱的SPI)來訪問源代碼并發送生成的字節碼。 如果我們在內存中有代碼,則可以有以下代碼( 來自此文件 ):
public Class<?> compile(String sourceCode, String canonicalClassName)throws Exception {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();List<JavaSourceFromString> sources = new LinkedList<>();String className = calculateSimpleClassName(canonicalClassName);sources.add(new JavaSourceFromString(className, sourceCode));StringWriter sw = new StringWriter();MemoryJavaFileManager fm = new MemoryJavaFileManager(compiler.getStandardFileManager(null, null, null));JavaCompiler.CompilationTask task = compiler.getTask(sw, fm, null,null, null, sources);Boolean compilationWasSuccessful = task.call();if (compilationWasSuccessful) {ByteClassLoader byteClassLoader = new ByteClassLoader(new URL[0],classLoader, classesByteArraysMap(fm));Class<?> klass = byteClassLoader.loadClass(canonicalClassName);byteClassLoader.close();return klass;} else {compilerErrorOutput = sw.toString();return null;}}- 該代碼是開源項目Java源代碼編譯器(jscc)的一部分 ,位于文件Compiler.java中 。
編譯器實例可通過ToolProvider并且要創建編譯任務,我們必須調用getTask() 。 該代碼通過字符串編寫器將錯誤寫入字符串。 文件管理器( fm )是在同一程序包中實現的,它只是將文件作為字節數組存儲在映射中,其中的鍵是“文件名”。 這是類加載器在稍后加載類時將獲取字節的位置。 該代碼不提供任何可診斷的偵聽器(請參見RT中Java編譯器的文檔),編譯器選項或注釋處理器要處理的類。 這些都是空值。 最后一個參數是要編譯的源代碼列表。 我們在此工具中僅編譯一個類,但是由于編譯器API是通用的并且需要可迭代的源,因此我們提供了一個列表。 由于存在另一種抽象級別,因此此列表包含JavaSourceFromString 。
要開始編譯,必須“調用”創建的任務,如果編譯成功,則從一個或多個生成的字節數組中加載類。 請注意,如果在我們編譯的頂級類中有嵌套類或內部類,則編譯器將創建幾個類。 這就是為什么即使只編譯一個源類,我們也必須維護類的整個映射,而不是單個字節數組。 如果編譯不成功,則錯誤輸出將存儲在字段中并可以查詢。
該類的使用非常簡單,您可以在單元測試中找到示例:
private String loadJavaSource(String name) throws IOException {InputStream is = this.getClass().getResourceAsStream(name);byte[] buf = new byte[3000];int len = is.read(buf);is.close();return new String(buf, 0, len, "utf-8");} ...@Testpublic void given_PerfectSourceCodeWithSubClasses_when_CallingCompiler_then_ProperClassIsReturned()throws Exception {final String source = loadJavaSource("Test3.java");Compiler compiler = new Compiler();Class<?> newClass = compiler.compile(source, "com.javax0.jscc.Test3");Object object = newClass.newInstance();Method f = newClass.getMethod("method");int i = (int) f.invoke(object, null);Assert.assertEquals(1, i);}請注意,以這種方式創建的類僅在運行時可用于代碼。 例如,您可以創建對象的不可變版本。 如果要在編譯期間提供可用的類,則應使用scriapt之類的注釋處理器。
翻譯自: https://www.javacodegeeks.com/2016/03/java-compile-java.html
總結
以上是生活随笔為你收集整理的Java用Java编译的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java工程师成神之路 | 2022正式
- 下一篇: 放鞭炮的意义 放鞭炮的意义是什么