- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- 教你怎么實(shí)現java語(yǔ)言的在線(xiàn)編譯
首先在運行java程序之前,肯定要想辦法把.java
的文件使用編譯器,編譯成.class
的字節碼文件。
運氣好的是,強大的Java已經(jīng)具備類(lèi)似的API,就是JavaCompiler
類(lèi),下面做一點(diǎn)簡(jiǎn)單介紹:
JavaCompiler是java語(yǔ)言自帶的一個(gè)接口,大概是一個(gè)對Java編譯器的一個(gè)抽象,通過(guò)ToolProvider 類(lèi)的靜態(tài)方法獲取其實(shí)現對象:
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對象,是一個(gè)接口實(shí)現類(lèi)
public final class JavacTool implements JavaCompiler {
這個(gè)類(lèi)實(shí)現了run
方法
public interface Tool { int run(InputStream in, OutputStream out, OutputStream err, String... arguments); }
各個(gè)參數的意思分別是
in
java編譯器提供信息
out
用于獲取輸出信息
err
用于獲取錯誤信息
arguments
編譯的文件(路徑)
前面三個(gè)參數如果,為null
則會(huì )用默認標準輸入輸出代替。網(wǎng)上到處都搜的到不做累述。
于是就有了第一種在線(xiàn)編譯運行的實(shí)現思路,使用文件IO來(lái)動(dòng)態(tài)生成.java
格式的文件與路徑,然后寫(xiě)入代碼內容。
最初我便是打算姑且使用這種方式,由于數據封裝對象UserDto與Question都具有一個(gè)唯一的Id屬性,因此 xx.userId.questionId
似乎挺適合用來(lái)做生成文件的類(lèi)路徑的,類(lèi)名就可以統一學(xué)習leetcode使用Solution ,于是一番努力后寫(xiě)出了我的Compilerv1.0
然而這種方式就給人感覺(jué)很low,“java動(dòng)態(tài)編譯”聽(tīng)起來(lái)還挺屌的,結果一細看,就這?
而且,這樣的實(shí)現,每次前端給一個(gè)請求過(guò)來(lái)都要進(jìn)行文件讀寫(xiě)操作,如果之前沒(méi)有建立好相應路徑與文件,還得重新新建,于是,當用戶(hù)和題目多起來(lái)了以后那將是一個(gè)龐大的文件數量(最大值:用戶(hù)數X題目數X2),甚至并發(fā)量稍微有一點(diǎn)還不知道會(huì )出現什么問(wèn)題。
在線(xiàn)編譯最理想的情況是:
前端表單傳給你需要編譯的java文件字符串內容,然后將數據直接交給自定義編譯器,編譯器經(jīng)過(guò)編譯后返回Class對象,然后你再進(jìn)行相應操作。
為了實(shí)現這個(gè)功能,除了JavaCompiler
還需要去了解如下對象:
JavaFileObject
(大概就是java文件的抽象)JavaFileManager
(大概就是Java文件管理操作的封裝)相關(guān)內容是從上面博客鏈接學(xué)會(huì )的,我自己再做了些改動(dòng):
1.需要自定義一個(gè)JavaFileObject
重寫(xiě)一些方法
2.需要自定義一個(gè)JavaFileManager
重寫(xiě)一些方法
大致原理就是,由于Java封裝的特性,只要類(lèi)的行為正確,可以關(guān)心類(lèi)的內部細節,所以,獲取.java
文件內容,最初是從文件中獲取,如果我們重寫(xiě)相應方法,意味著(zhù)我們可以將要編譯的String內容,直接返回給相應處理程序,只要調用相應方法,返回的內容正確,其實(shí)并不用關(guān)心,數據到底是從哪來(lái)的。
下面是我的
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;
重寫(xiě)了兩個(gè)構造器方法:
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è)構造方法:用于自己創(chuàng )建對象時(shí)使用,調用父類(lèi)構造方法的同時(shí)初始化屬性:javaCode
完成初始化以后,相關(guān)對象會(huì )調用重寫(xiě)的方法
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return this.javaCode; }
然后進(jìn)行編譯。
第二個(gè)構造方法是給相關(guān)API調用,然后會(huì )調用重寫(xiě)的方法
@Override public OutputStream openOutputStream() throws IOException { return this.outputStream; }
將編譯結果寫(xiě)入提供的IO流
重寫(xiě)好的JavaFileObject類(lèi)配合自定義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ì )調用
public JavaFileObject getJavaFileForOutput
此時(shí),內置IO流屬性會(huì )被初始化,然后寫(xiě)入編譯的Class對象的二進(jìn)制流信息,最后自定義一下類(lèi)加載器的findClass
方法,利用loadClass
方法獲取編譯后得到的結果
cls=manager.getClassLoader(null).loadClass(className);
由于沒(méi)有實(shí)際文件,最后會(huì )由下面的代碼,尋找到需要加載到的類(lèi)信息就會(huì )調用之前的重寫(xiě)的findClass方法得到Class 對象(defineClass方法用來(lái)將二進(jìn)制流信息還原為Class對象)
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í)就介紹這么多,剩下的內容以后有時(shí)間再整理,如果又沒(méi)說(shuō)清楚的地方歡迎指正。
到此這篇關(guān)于教你怎么實(shí)現java語(yǔ)言的在線(xiàn)編譯的文章就介紹到這了,更多相關(guān)實(shí)現java語(yǔ)言的在線(xiàn)編譯內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站