更新時間:2021-11-18 10:50:03 來源:動力節點 瀏覽1026次
在Java開發工具中,有一種是基于Spring Boot的Java在線編譯工具,下面小編來給大家介紹。
程序運行流程圖如下
接下來開始具體分析每一步的實現方法
想要實現在線運行Java代碼的需求,我們首先需要了解Java程序正常的編譯和運行流程。
首先源代碼文件(.java)經由編譯器編譯成字節碼
例如JDK中的javac命令就是實現字節碼生成技術的程序
接下來有Java虛擬機解釋并運行字節碼文件,運行過程有分為兩個步驟
類的加載
應用程序運行后,系統會啟動一個虛擬機進程。JVM進程在類的加載階段首先會通過一個類的全限定類名獲取定義此類的二進制字節流,然后將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構,并且在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據訪問入口。
類加載的相關的內容比較復雜,生成對應的Class對象后還會進行驗證、準備、解析、初始化等一系列步驟才算加載完成,但考慮到篇幅問題這里就不再展開說明了。
類的執行
當類加載完成后JVM就可以找到main方法執行了。
本項目中使用反射來完成這一步驟。
明確了以上步驟后,我們發現有三個問題需要解決:
如何編譯提交到服務器的Java代碼?
在本地運行Java代碼的時候我們可以選用Javac命令編譯。對于本項目而言,這種方式需要我們先將源代碼寫入一個.java文件,再編譯得到.class文件。但是這樣一來不僅非常耗時,而且還會生成額外的文件,導致服務器環境被污染。因此我們選擇使用JDK1.6以后添加的動態編譯API來解決這一問題。
如何執行編譯之后的代碼?
一段程序往往不是編寫、運行一次就能達到效果的。同一個類可能需要反復的修改、提交、運行。另外,提交的類也要能訪問服務端的其他類庫才行,對于這一問題,需要我們自己編寫類加載器來實現需求。
如何收集Java代碼的執行結果?
我們需要把程序向標準輸出(System.out)和標準錯誤輸出(System.err)中打印的信息收集起來返回給客戶端。但是標準輸出設備是整個虛擬機進程全局共享的資源。如果使用System.setOut()/System.setErr()方法將輸出流重定向到自己定義的PrintStream上固然可以收集信息,但在多線程情況下這樣會連帶其他線程的信息一起收集了,這顯然不是我們希望看到的。因此我們選擇將程序中的System替換為我們自己寫的HackSystem類。
也就是說,我們的重點在于實現編譯模塊和運行模塊。 在理清以上思路后,我們就可以正式開始代碼的編寫了。
在正式開始編碼前還要羅嗦一下,本項目選擇使用Spring Boot僅僅是看中了它在開發web應用時的方便、快捷,項目中并不會涉及太多框架方面的知識。
如果對于Spring Boot的自動配置原理感興趣,可以閱讀下筆者寫的另一篇文章,記錄了筆者對于Spring Boot自動配置原理的一些粗淺認識,歡迎各位大神斧正。
使用動態編譯的方式可以直接在內存中對一個Java程序進行編譯并輸出到內存中,提高程序運行效率的同時還不會污染服務器環境,可謂一舉兩得。具體實現步驟如下。
關于動態編譯的API全部放在javax.tools包下,本項目中主要涉及到的類和接口如下所示:
編譯器:
JavaCompiler
ToolProvider
源代碼文件:
JavaFileObject
SimpleJavaFIleObject
文件管理器:
JavaFileManager
StandardJavaFileManager
ForwardingJavaFileManager
收集診斷信息:
DiagnosticListener
DiagnosticCollector
接下來開始具體介紹實現動態編譯的步驟
準備編譯器對象
只有一種方法:
//獲取Java語言編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//開始執行編譯,通過傳入自己的JavaFileManager為編譯器創建存放字節碼的JavaFIleObject對象
Boolean result = compiler.getTask(null,javaFileManager,compileCollector,
null,null, Arrays.asList(sourceJavaFileObject)).call();
關于ToolProvider這里有一個坑,如果使用的是OpenJDK,tools.jar文件是放在%JAVA_HOME%/lib下的,運行起來就會報空指針異常。因為啟動java的目錄默認是%JAVA_HOME%/jre/bin/java.exe,這個目錄的lib目錄為%JAVA_HOME%/jre/lib,里面沒有tools.jar。因此要么把文件拷到指定的lib下,要么干脆使用Oracle JDK也是一切正常。
可以看到執行編譯這個方法要填一大堆參數,這些參數就是我們實現在內存中編譯源代碼的關鍵。
API中對于這個方法參數的解釋如下
JavaCompiler.CompilationTask getTask(Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<String> classes,
Iterable<? extends JavaFileObject> compilationUnits)
out - 用于編譯器的附加輸出; 如果為null使用的就是使用System.err
fileManager - 文件管理器; 如果null使用編譯器的標準文件管理器
diagnosticListener - 診斷信息收集器; 如果為null則使用編譯器的默認方法來報告診斷
options - 編譯器選項, null表示沒有選項
classes - 通過注釋處理類的名稱, null表示沒有類名
compilationUnits - 編譯單元, null表示無編譯單位
大家如果想了解更多相關知識,不妨來關注一下動力節點的Java在線學習,里面的課程內容豐富,從入門到精通,適合小白學習,希望對大家能夠有所幫助。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習