教你怎么實(shí)現(xiàn)java語(yǔ)言的在線編譯
一、前言
- 使用過(guò)leetcode或者類似在線編譯網(wǎng)站功能的人,或許會(huì)比較感興趣,關(guān)于在線編譯的實(shí)現(xiàn)原理,由于我比較頭鐵,所以一沖動(dòng)之下畢業(yè)設(shè)計(jì)的項(xiàng)目選擇制作一個(gè)類似于在線編譯的一個(gè)網(wǎng)站。
- 在決定做這個(gè)之前,大概對(duì)這方面的東西一竅不通,網(wǎng)上的資料很多也是比較千篇一律,給我這種萌新帶來(lái)的難度不是一點(diǎn)半點(diǎn),當(dāng)然,最終收獲還是挺大的,所以想寫(xiě)一點(diǎn)東西,作為梳理,也給以后想學(xué)的人做一個(gè)參考作用(其實(shí)在寫(xiě)的過(guò)程中還是踩了一些坑的)。
- 最終,其實(shí)成果挺水的,做出來(lái)的成品,就只是實(shí)現(xiàn)了一個(gè)簡(jiǎn)陋的Java語(yǔ)言的在線編譯功能,這里也想吐槽一下,其實(shí)leetcode,支持那么多語(yǔ)言的在線編譯真的挺厲害的。
二、前期準(zhǔn)備
首先在運(yùn)行java程序之前,肯定要想辦法把.java
的文件使用編譯器,編譯成.class
的字節(jié)碼文件。
運(yùn)氣好的是,強(qiáng)大的Java已經(jīng)具備類似的API,就是JavaCompiler
類,下面做一點(diǎn)簡(jiǎn)單介紹:
JavaCompiler是java語(yǔ)言自帶的一個(gè)接口,大概是一個(gè)對(duì)Java編譯器的一個(gè)抽象,通過(guò)ToolProvider 類的靜態(tài)方法獲取其實(shí)現(xiàn)對(duì)象:
public interface JavaCompiler extends Tool, OptionChecker
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
稍微看一下源碼
private static final String defaultJavaCompilerName = "com.sun.tools.javac.api.JavacTool"; private static synchronized ToolProvider instance() { if (instance == null) instance = new ToolProvider(); return instance; } /** * Gets the Java™ programming language compiler provided * with this platform. * @return the compiler provided with this platform or * {@code null} if no compiler is provided */ public static JavaCompiler getSystemJavaCompiler() { return instance().getSystemTool(JavaCompiler.class, defaultJavaCompilerName); }
可以知道,返回的是一個(gè)JavacTool對(duì)象,是一個(gè)接口實(shí)現(xiàn)類
public final class JavacTool implements JavaCompiler {
這個(gè)類實(shí)現(xiàn)了run
方法
public interface Tool { int run(InputStream in, OutputStream out, OutputStream err, String... arguments); }
各個(gè)參數(shù)的意思分別是
in
java編譯器提供信息
out
用于獲取輸出信息
err
用于獲取錯(cuò)誤信息
arguments
編譯的文件(路徑)
前面三個(gè)參數(shù)如果,為null
則會(huì)用默認(rèn)標(biāo)準(zhǔn)輸入輸出代替。網(wǎng)上到處都搜的到不做累述。
三、JavaCompiler V1.0
于是就有了第一種在線編譯運(yùn)行的實(shí)現(xiàn)思路,使用文件IO來(lái)動(dòng)態(tài)生成.java
格式的文件與路徑,然后寫(xiě)入代碼內(nèi)容。
最初我便是打算姑且使用這種方式,由于數(shù)據(jù)封裝對(duì)象UserDto與Question都具有一個(gè)唯一的Id屬性,因此 xx.userId.questionId
似乎挺適合用來(lái)做生成文件的類路徑的,類名就可以統(tǒng)一學(xué)習(xí)leetcode使用Solution ,于是一番努力后寫(xiě)出了我的Compilerv1.0
然而這種方式就給人感覺(jué)很low,“java動(dòng)態(tài)編譯”聽(tīng)起來(lái)還挺不錯(cuò)的,結(jié)果一細(xì)看,就這?
而且,這樣的實(shí)現(xiàn),每次前端給一個(gè)請(qǐng)求過(guò)來(lái)都要進(jìn)行文件讀寫(xiě)操作,如果之前沒(méi)有建立好相應(yīng)路徑與文件,還得重新新建,于是,當(dāng)用戶和題目多起來(lái)了以后那將是一個(gè)龐大的文件數(shù)量(最大值:用戶數(shù)X題目數(shù)X2),甚至并發(fā)量稍微有一點(diǎn)還不知道會(huì)出現(xiàn)什么問(wèn)題。
四、JavaCompiler V2.0
在線編譯最理想的情況是:
前端表單傳給你需要編譯的java文件字符串內(nèi)容,然后將數(shù)據(jù)直接交給自定義編譯器,編譯器經(jīng)過(guò)編譯后返回Class對(duì)象,然后你再進(jìn)行相應(yīng)操作。
為了實(shí)現(xiàn)這個(gè)功能,除了JavaCompiler
還需要去了解如下對(duì)象:
JavaFileObject
(大概就是java文件的抽象)JavaFileManager
(大概就是Java文件管理操作的封裝)
相關(guān)內(nèi)容是從上面博客鏈接學(xué)會(huì)的,我自己再做了些改動(dòng):
1.需要自定義一個(gè)JavaFileObject
重寫(xiě)一些方法
2.需要自定義一個(gè)JavaFileManager
重寫(xiě)一些方法
大致原理就是,由于Java封裝的特性,只要類的行為正確,可以關(guān)心類的內(nèi)部細(xì)節(jié),所以,獲取.java
文件內(nèi)容,最初是從文件中獲取,如果我們重寫(xiě)相應(yīng)方法,意味著我們可以將要編譯的String內(nèi)容,直接返回給相應(yīng)處理程序,只要調(diào)用相應(yīng)方法,返回的內(nèi)容正確,其實(shí)并不用關(guān)心,數(shù)據(jù)到底是從哪來(lái)的。
下面是我的
五、JavaFileObject實(shí)現(xiàn)
public class JavaFileObjectBean extends SimpleJavaFileObject { /** * Construct a SimpleJavaFileObject of the given kind and with the * given URI. * * @param uri the URI for this file object * @param kind the kind of this file object */ private String javaCode; private ByteArrayOutputStream outputStream; public JavaFileObjectBean(String className, String javaCode) { super(URI.create("string:///"+className.replace(".","/")+Kind.SOURCE.extension), Kind.SOURCE); // System.out.println("string:///" + className.replace(".", "/") + Kind.SOURCE.extension); this.javaCode=javaCode; //this.outputStream=new ByteArrayOutputStream(); } protected JavaFileObjectBean(String className, Kind kind) { super(URI.create("string:///"+className.replace(".","/")+kind.extension), kind); // System.out.println("!!"); this.outputStream=new ByteArrayOutputStream(); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return this.javaCode; } @Override public OutputStream openOutputStream() throws IOException { return this.outputStream; } public byte[] getBytes(){ return this.outputStream.toByteArray(); } }
繼承自SimpleJavaFileObject
public class SimpleJavaFileObject implements JavaFileObject
新加了幾個(gè)屬性
private String javaCode; private ByteArrayOutputStream outputStream;
- javaCode:用來(lái)保存需要編譯的Java文件內(nèi)容
- outputStream:用來(lái)保存編譯后,Class對(duì)象的二進(jìn)制流
重寫(xiě)了兩個(gè)構(gòu)造器方法:
public JavaFileObjectBean(String className, String javaCode) { super(URI.create("string:///"+className.replace(".","/")+Kind.SOURCE.extension), Kind.SOURCE); // System.out.println("string:///" + className.replace(".", "/") + Kind.SOURCE.extension); this.javaCode=javaCode; //this.outputStream=new ByteArrayOutputStream(); } protected JavaFileObjectBean(String className, Kind kind) { super(URI.create("string:///"+className.replace(".","/")+kind.extension), kind); // System.out.println("!!"); this.outputStream=new ByteArrayOutputStream(); }
第一個(gè)構(gòu)造方法:用于自己創(chuàng)建對(duì)象時(shí)使用,調(diào)用父類構(gòu)造方法的同時(shí)初始化屬性:javaCode
完成初始化以后,相關(guān)對(duì)象會(huì)調(diào)用重寫(xiě)的方法
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return this.javaCode; }
然后進(jìn)行編譯。
第二個(gè)構(gòu)造方法是給相關(guān)API調(diào)用,然后會(huì)調(diào)用重寫(xiě)的方法
@Override public OutputStream openOutputStream() throws IOException { return this.outputStream; }
將編譯結(jié)果寫(xiě)入提供的IO流
重寫(xiě)好的JavaFileObject類配合自定義JavaFileManager使用
public class JavaFileManagerBean extends ForwardingJavaFileManager { private JavaFileObjectBean javaFileObjectBean; /** * Creates a new instance of ForwardingJavaFileManager. * * @param fileManager delegate to this file manager */ protected JavaFileManagerBean(JavaFileManager fileManager) { super(fileManager); //this.javaFileObjectBean= new JavaFileObjectBean(); } @Override public ClassLoader getClassLoader(Location location) { return new SecureClassLoader(){ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = javaFileObjectBean.getBytes(); return super.defineClass(name,bytes,0,bytes.length); } }; } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { this.javaFileObjectBean = new JavaFileObjectBean(className,kind); return this.javaFileObjectBean; } }
相關(guān)API會(huì)調(diào)用
public JavaFileObject getJavaFileForOutput
此時(shí),內(nèi)置IO流屬性會(huì)被初始化,然后寫(xiě)入編譯的Class對(duì)象的二進(jìn)制流信息,最后自定義一下類加載器的findClass
方法,利用loadClass
方法獲取編譯后得到的結(jié)果
cls=manager.getClassLoader(null).loadClass(className);
由于沒(méi)有實(shí)際文件,最后會(huì)由下面的代碼,尋找到需要加載到的類信息就會(huì)調(diào)用之前的重寫(xiě)的findClass方法得到Class 對(duì)象(defineClass方法用來(lái)將二進(jìn)制流信息還原為Class對(duì)象)
if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }
暫時(shí)就介紹這么多,剩下的內(nèi)容以后有時(shí)間再整理,如果又沒(méi)說(shuō)清楚的地方歡迎指正。
到此這篇關(guān)于教你怎么實(shí)現(xiàn)java語(yǔ)言的在線編譯的文章就介紹到這了,更多相關(guān)實(shí)現(xiàn)java語(yǔ)言的在線編譯內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Java的編譯執(zhí)行與解釋執(zhí)行
- java實(shí)現(xiàn)動(dòng)態(tài)編譯并動(dòng)態(tài)加載
- 改善Java代碼之慎用java動(dòng)態(tài)編譯
- java利用JEXL實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式編譯
- java編譯命令基礎(chǔ)知識(shí)點(diǎn)
- java編譯后的文件出現(xiàn)xx$1.class的原因及解決方式
- java編譯器和JVM的區(qū)別
- 利用 Docker 構(gòu)建簡(jiǎn)單的 java 開(kāi)發(fā)編譯環(huán)境的方法詳解
- 手動(dòng)編譯并運(yùn)行Java項(xiàng)目實(shí)現(xiàn)過(guò)程解析
- IDEA不編譯除了.java之外的文件的解決辦法(推薦)
- 2020年支持java8的Java反編譯工具匯總(推薦)
- IDEA 中使用 ECJ 編譯出現(xiàn) java.lang.IllegalArgumentException的錯(cuò)誤問(wèn)題
相關(guān)文章
SpringBoot實(shí)現(xiàn)IP地址解析的示例代碼
本篇帶大家實(shí)踐在springboot項(xiàng)目中獲取請(qǐng)求的ip與詳細(xì)地址,我們的很多網(wǎng)站app中都已經(jīng)新增了ip地址顯示,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01詳解如何在低版本的Spring中快速實(shí)現(xiàn)類似自動(dòng)配置的功能
這篇文章主要介紹了詳解如何在低版本的Spring中快速實(shí)現(xiàn)類似自動(dòng)配置的功能,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05Spring Security 密碼驗(yàn)證動(dòng)態(tài)加鹽的驗(yàn)證處理方法
小編最近在改造項(xiàng)目,需要將gateway整合security在一起進(jìn)行認(rèn)證和鑒權(quán),今天小編給大家分享Spring Security 密碼驗(yàn)證動(dòng)態(tài)加鹽的驗(yàn)證處理方法,感興趣的朋友一起看看吧2021-06-06Java多線程異步調(diào)用性能調(diào)優(yōu)方法詳解
這篇文章主要為大家詳細(xì)介紹了Java多線程異步調(diào)用性能調(diào)優(yōu),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問(wèn)題解決方法
這篇文章主要介紹了IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11