日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

面试干货 | Java 能否自定义一个类叫 java.lang.System?

發(fā)布時間:2025/3/11 java 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试干货 | Java 能否自定义一个类叫 java.lang.System? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文由讀者 apdoer 投稿,apdoer 是一個極具鉆研精神的 Java 猿,技術(shù)牛X頭發(fā)茂盛!?

博客地址:https://blog.csdn.net/m0_43452671

緣起:一個面試題

最近在上下班地鐵刷博客,無意刷到一個面試題,號稱很多程序員的烈士公墓:

java 能否自己寫一個類叫 java.lang.System

博主也提供了相關(guān)的答案:

一般情況下是不可以的,但是可以通過特殊的處理來達到目的,這個特殊的處理就是自己寫個類加載器來加載自己寫的這個 java.lang.System 類。

然后隨手又刷了幾個,基本雷同,看到的博客都是在講 java 類加載的雙親委托機制, 一個類在需要加載時,會向上委托,直到最上層的 bootstrapClassLoader ,然后最上層的 bootstrapClassLoader 如果能在自己對應(yīng)的目錄加載就加載,不能就向下查找。

而 bootstrapClassLoader 會加載系統(tǒng)默認的 System 類,所以我們自定義的就不會被加載。

但是我們自定義一個類加載器加載特定路徑的,避開 jvm 默認的三個類加載器的加載路徑,就可以使我們的自定義 System 類被加載。

可是真的是這樣嗎?

為了弄清楚這個問題,我又看了下類加載。

什么是類加載

  • 類加載指的是將類 Class 文件讀入內(nèi)存,并為之創(chuàng)建一個 java.lang.Class 對象, class 文件被載入到了內(nèi)存之后,才能被其它 class 所引用

  • jvm 啟動的時候,并不會一次性加載所有的 class 文件,而是根據(jù)需要去動態(tài)加載

  • java 類加載器是 jre 的一部分,負責動態(tài)加載 java 類到 java 虛擬機的內(nèi)存

  • 類的唯一性由類加載器和類共同決定

還了解到系統(tǒng)的三種類加載器:

  • AppClassLoader?: 也稱為 SystemAppClass 加載當前應(yīng)用的 classpath 的所有類。

  • ExtClassLoader?: 擴展的類加載器,加載目錄 %JRE_HOME%\lib\ext 目錄下的 jar 包和 class 文件。還可以加載?-D java.ext.dirs?選項指定的目錄。

  • BoostrapClassLoader?: 最頂層的加載類,主要加載核心類庫, %JRE_HOME%\lib 下的 rt.jar、resources.jar、charsets.jar 和 class 等。另外需要注意的是可以通過啟動 jvm 時指定 -Xbootclasspath 和路徑來改變 Bootstrap ClassLoader 的加載目錄。比如 java -Xbootclasspath/a:path 被指定的文件追加到默認的 bootstrap 路徑中。

瞄一眼源碼,在Launcher類中

public class Launcher {private static URLStreamHandlerFactory factory = new Launcher.Factory();private static Launcher launcher = new Launcher();private static String bootClassPath = System.getProperty("sun.boot.class.path");private ClassLoader loader;private static URLStreamHandler fileHandler;public static Launcher getLauncher() {return launcher;}public Launcher() {// 創(chuàng)建ExtClassLoaderLauncher.ExtClassLoader var1;var1 = Launcher.ExtClassLoader.getExtClassLoader();//創(chuàng)建AppClassLoaderthis.loader = Launcher.AppClassLoader.getAppClassLoader(var1);//設(shè)置AppClassLoader為線程上下文類加載器Thread.currentThread().setContextClassLoader(this.loader);}public ClassLoader getClassLoader() {return this.loader;}public static URLClassPath getBootstrapClassPath() {return Launcher.BootClassPathHolder.bcp;}//AppClassLoaderstatic class AppClassLoader extends URLClassLoader {public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {final String var1 = System.getProperty("java.class.path");public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {}//ExtClassLoaderstatic class ExtClassLoader extends URLClassLoader {private static volatile Launcher.ExtClassLoader instance;public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {}//創(chuàng)建ExtClassLoaderprivate static Launcher.ExtClassLoader createExtClassLoader() throws IOException {}private static File[] getExtDirs() {String var0 = System.getProperty("java.ext.dirs");File[] var1;

這段源碼有以下幾點

  • Launcher?類在構(gòu)造函數(shù)初始化了?ExtClassLoader?和?AppClassLoader?并設(shè)置?AppClassLoader?為線程上下文類加載器。

  • 代碼里面沒有告訴我們?BoostrapClassLoader?從哪里來的,但卻為其指定了要加載 class 文件的路徑?sun.boot.class.path?。

  • BoostrapClassLoader 是由 c++ 編寫的,內(nèi)嵌在 jvm 中,所以不能顯示的看到他的存在【這個不是從源碼中得到】。

實踐出真知

我們通過代碼來檢驗下上面的理論。

類加載器的父子關(guān)系

public class Test {public static void main(String[] args) {System.out.println(Test.class.getClassLoader());System.out.println(Test.class.getClassLoader().getParent());System.out.println(Test.class.getClassLoader().getParent().getParent());} }

這段代碼我們可以看到類加載器的父子關(guān)系, APPClassLoader->ExtClassLoader->BoostrapClassLoader , 但是 BoostrapClassLoader 無法顯示的獲取到,只能看到是個 null 。

源碼中的路徑到底加載哪些目錄

  • sun.boot.class.path

public static void main(String[] args) {String property = System.getProperty("sun.boot.class.path");//BoostrapClassLoaderString[] split = property.split(";");Arrays.asList(split).forEach(s -> System.out.println(s)); }

可以看到是 jre/lib 目錄下一些核心 jar

  • java.ext.dirs

public static void main(String[] args) {String property = System.getProperty("java.ext.dirs");//ExtClassLoaderString[] split = property.split(";");Arrays.asList(split).forEach(s -> System.out.println(s)); }

  • java.class.path

public static void main(String[] args) {String property = System.getProperty("java.class.path");//AppClassLoaderString[] split = property.split(";");Arrays.asList(split).forEach(s -> System.out.println(s)); }

可以看到,各個加載器加載的對應(yīng)路徑和前面的介紹是吻合的

類加載的雙親委托機制

這里直接來一張圖(processon 圖庫滿了,這個先將就下):

如果看不太懂可以看下以下解釋

  • 一個 class 文件發(fā)送請求加載,會先找到自定義的類加載器,當然這里沒畫出來。

  • APPClassLoader 得到加載器請求后,向上委托交給 ExtClassLoader , ExtClassLoader 同理會交給 BoostrapClassLoader ,這是向上委托方向

  • 最終到達 BoostrapClassLoader ,會先在緩存中找,沒有就嘗試在自己能加載的路徑去加載,找不到就交給 ExtClassLoader ,同理一直到用戶自定義的 ClassLoader ,這就是向下查找方向

  • 前面說的類的唯一性由類和類加載器共同決定, 這樣保證了確保了類的唯一性。

弄清楚這些,我們可以開始驗證自定義的類加載器是否可以加載我們自定義的這個System類了

自定義類加載器

  • 新建一個?MyClassLoader?繼承?ClassLoader?,并重寫 loadclass 方法

package org.apder; import java.io.InputStream; public class MyClassLoader extends ClassLoader{public MyClassLoader(){super(null);}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {String className = null;if (name != null && !"".equals(name)){if (name.startsWith("java.lang")){className = new StringBuilder("/").append(name.replace('.','/')).append(".class").toString();}else {className = new StringBuffer(name.substring(name.lastIndexOf('.')+1)).append(".class").toString();}System.out.println(className);InputStream is = getClass().getResourceAsStream(className);System.out.println(is);if (is == null) return super.loadClass(name);byte[] bytes = new byte[is.available()];is.read(bytes);return defineClass(name,bytes,0,bytes.length);}return super.loadClass(name);} }

這里的代碼很容易看懂,就不贅述了。

  • 測試

由于 System 需要用于打印獲取結(jié)果,這里就用同屬 lang 包的 Long 類:

public class Long {public void testClassLoader(){System.out.println("自定義Long類被"+Long.class.getClassLoader()+"加載了");}public static void main(String[] args) {System.out.println("Long");} }

運行自定義 Long 類中 main 方法 報錯如下:

出錯原因很簡單,這個自定義的 Long 類申請加載后,會被委托到 BoostrapClassLoader,BoostrapClassLoader 會在向下查找的過程中找到 rt.jar 中的 java.lang.Long 類并加載,執(zhí)行 main 方法時,找不到 main 方法,所以報找不到 main 方法。

public class MyLong {public void testClassLoader(){System.out.println("自定義Math類被"+MyLong.class.getClassLoader()+"加載了");}public static void main(String[] args) {System.out.println("mylong");} }

我們再定義一個自定義的 java.lang.MyLong 類,執(zhí)行 main 方法,報錯如下

很明顯的堆棧信息,禁止使用的包名 java.lang ,我們點進去 preDefineClass 看看:

private ProtectionDomain preDefineClass(String name,ProtectionDomain pd){if (!checkName(name))throw new NoClassDefFoundError("IllegalName: " + name);if ((name != null) && name.startsWith("java.")) {throw new SecurityException("Prohibited package name: " + name.substring(0, name.lastIndexOf('.')));}if (pd == null) {pd = defaultDomain;}if (name != null) checkCerts(name, pd.getCodeSource());return pd; }

可以看到,當如果類的全路徑名以 java. 開頭時,就會報錯,看到這里,開頭的答案你是否有了結(jié)果呢?

我們梳理一下過程,如果用自定義的類加載器加載我們自定義的類

  • 會調(diào)用自定義類加載器的?loadClass?方法。

  • 而我們自定義的 classLoader 必須繼承 ClassLoader,loadClass 方法會調(diào)用父類的 defineClass 方法。

  • 而父類的這個 defineClass 是一個 final 方法,無法被重寫

  • 所以自定義的 classLoader 是無論如何也不可能加載到以?java.?開頭的類的。

到這里,最開始的問題已經(jīng)有了答案。我們無法自定義一個叫 java.lang.System 的類。

思考

如果我把 MyLong 打成 jar 放到 BoostrapClassLoader 的加載路徑呢?讓 BoostrapclassLoader 去加載,具體操作如下,在 jdk 的 jre 目錄下創(chuàng)建 classes 目錄,然后把 MyLong.jar 復制進去,再通過 vmOptions 追加這個 classes 目錄以使 BoostrapClassLoader 加載:

可以看到仍然加載不了,如果能加載,在控制臺是會有 load 信息的,如果不是 java.lang.Long ,是可以跨過 APPClassLoader 和 ExtClassLoader 來讓 boostraPClassloader 來加載的,這里就不演示了,操作很簡單。

下面是vm參數(shù)

-Xbootclasspath/a:c:\classloader.jar -verbose

由一個面試題引起的類加載器思考,既然已經(jīng)寫到這里,干脆把線程上下文類加載器也一并學習了。

拓展線程上下文類加載器

為什么不和前面三種類加載器放在一起說呢,這個線程上下文類加載器只是一個概念,是一個成員變量,而前三種是確切存在的,是一個類,我們來看一下 Thread 的源碼:

public class Thread implements Runnable {private ClassLoader contextClassLoader;public void setContextClassLoader(ClassLoader cl) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setContextClassLoader"));}contextClassLoader = cl;}@CallerSensitivepublic ClassLoader getContextClassLoader() {if (contextClassLoader == null)return null;SecurityManager sm = System.getSecurityManager();if (sm != null) {ClassLoader.checkClassLoaderPermission(contextClassLoader,Reflection.getCallerClass());}return contextClassLoader;} }

特點

  • 線程上下文類加載器是一個成員變量,可以通過相應(yīng)的方法來設(shè)置和獲取。

  • 每個線程都有一個線程類加載器,默認是?AppClassLoader?。

  • 子線程默認使用父線程的?ClassLoader?,除非子線程通過上面的?setContextClassLoader?來設(shè)置。

測試

針對以上兩點簡單測試一下:

public class Test {public static void main(String[] args) {Thread thread = new Thread(()->{});System.out.println(thread.getContextClassLoader());thread.setContextClassLoader(Test.class.getClassLoader().getParent());System.out.println(thread.getContextClassLoader());} }

public class Test {public static void main(String[] args) {Thread thread = new Thread(()->{});Thread.currentThread().setContextClassLoader(Test.class.getClassLoader().getParent());thread.setContextClassLoader(Test.class.getClassLoader().getParent());System.out.println(thread.getContextClassLoader());} }

可以證明以上三點

總結(jié)

  • java 三種類加載器

  • 一條主線-----路徑

  • 一個機制->雙親委托

  • 兩個方向->向上委托,向下查找

好了,本文就先介紹到這里,有問題歡迎留言討論。

【End】

查看更多面試題內(nèi)容,請訪問《Java最常見200+面試題全解析》,它包含的模塊有:

  • Java、JVM?最常見面試題解析

  • Spring、Spring?MVC、MyBatis、Hibernate?面試題解析

  • MySQL、Redis?面試題解析

  • RabbitMQ、Kafka、Zookeeper?面試解析

  • 微服務(wù)?Spring?Boot、Spring?Cloud?面試解析

掃描下面二維碼付費閱讀

關(guān)注下方二維碼,訂閱更多精彩內(nèi)容。

轉(zhuǎn)發(fā)朋友圈,是對我最大的支持。

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的面试干货 | Java 能否自定义一个类叫 java.lang.System?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。