Java虛擬機(jī)-類加載器ClassLoader

1 類加載器

Java 的類加載,就是把字節(jié)碼格式“.class”文件加載到 JVM的方法區(qū),并在 JVM 的堆區(qū)建立一個(gè)java.lang.Class對(duì)象的實(shí)例,用來封裝Java類相關(guān)的數(shù)據(jù)和方法。 Class對(duì)象可以把它理解成業(yè)務(wù)類的模板,JVM根據(jù)這個(gè)模板來創(chuàng)建具體業(yè)務(wù)類對(duì)象實(shí)例。

JVM類加載使用懶加載的機(jī)制,也就是說并不是在啟動(dòng)時(shí)就把所有的“.class”文件都加載一遍,而是程序在運(yùn)行過程中用到了這個(gè)類才去加載。JVM 類加載是由類加載器來完成的,JDK類加載器的抽象基類為ClassLoader。

在 Java 虛擬機(jī)中,類的唯一性是由類加載器實(shí)例以及類的全名一同確定的。即便是同一串字節(jié)流,經(jīng)由不同的類加載器加載,也會(huì)得到兩個(gè)不同的類。在大型應(yīng)用中,我們往往借助這一特性,來運(yùn)行同一個(gè)類的不同版本。

案例

@Test
public  void  test() throws Exception {
    /** 獲取當(dāng)前Class對(duì)象的類加載器加載指定類,返回Class對(duì)象**/
    Class<?> aClass = ClassLoaderTest.class.getClassLoader().loadClass("jvm.Bootstrap");
    /** 通過Class對(duì)象獲取構(gòu)造函數(shù) **/
    Constructor<?> constructor = aClass.getConstructor();
    /** 通過反射構(gòu)造實(shí)例化類對(duì)象**/
    Bootstrap Bootstrap = (Bootstrap)constructor.newInstance();
    System.out.println(Bootstrap);
}

2 ClassLoader類結(jié)構(gòu)

image
  • ClassLoader:ClassLoader類加載器的基類,其內(nèi)部存在一個(gè)類型為ClassLoader屬性parent,用來實(shí)現(xiàn)類加載器之間的父子關(guān)系。
    public abstract class ClassLoader {
        ...
        //父類加載器
        private final ClassLoader parent;
  • URLClassLoader:URLClassLoader是ClassLoader子類,其內(nèi)部存一個(gè)類型為URLClassPath屬性為ucp,ucp負(fù)責(zé)管理被當(dāng)前類加載器加載Class文件資源路徑。
    public class URLClassLoader extends SecureClassLoader implements Closeable {
        private final URLClassPath ucp;

3 ClassLoader中核心方法

protected final Class<?> defineClass(String name, byte[] b, int  off, int len) throws ClassFormatError{ … }  

defineClass是個(gè)工具方法,它的職責(zé)是調(diào)用 native 方法把 Java 類的字節(jié)碼解析成一個(gè) Class 對(duì)象,所謂的 native 方法就是由 C 語言實(shí)現(xiàn)的方法,Java 通過 JNI 機(jī)制調(diào)用。

protected Class<?> findClass(String name) throws ClassNotFoundException { … }

findClass 方法的主要職責(zé)就是找到“.class”文件,.class”文件可能來自文件系統(tǒng)或者網(wǎng)絡(luò),找到后把“.class”文件讀到內(nèi)存中得到字節(jié)碼數(shù)組,然后調(diào)用 defineClass 方法得到 Class 對(duì)象。

public synchronized Class<?> loadClass(String name) throws ClassNotFoundException{ … }  

loadClass 是個(gè) public 方法,說明它才是對(duì)外提供服務(wù)的接口,具體實(shí)現(xiàn)也比較清晰:首先檢查這個(gè)類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則交給父加載器去加載。請(qǐng)你注意,這是一個(gè)遞歸調(diào)用,也就是說子加載器持有父加載器的引用,當(dāng)一個(gè)類加載器需要加載一個(gè) Java 類時(shí),會(huì)先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索 Java 類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時(shí),才會(huì)交還給子加載器加載,這就是雙親委托機(jī)制。

4 Java 虛擬機(jī)中類加載器

在JVM中定義了4類加載器分別為:啟動(dòng)(Bootstrap)類加載器,擴(kuò)展(Extension)類加載器系統(tǒng)(System)類加載器,以及用戶自定義加載器

image
4.1 啟動(dòng)(Bootstrap)類加載器

引導(dǎo)類加載器是負(fù)責(zé)加載并管理<JAVA_HOME>/lib 的核心類庫 -Xbootclasspath選項(xiàng)指定的jar包c(diǎn)lass文件對(duì)應(yīng)的Class對(duì)象。

啟動(dòng)(Bootstrap)類加載器是擴(kuò)展(Extension)類加載器的父加載器,是最高等級(jí)的加載器,由于啟動(dòng)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動(dòng)類加載器的引用,所以 不允許直接通過引用進(jìn)行操作。

//從系統(tǒng)屬性中獲取啟動(dòng)(Bootstrap)類加載器加載并管理核心類庫
System.getProperty("sun.boot.class.path")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\classes
4.2 擴(kuò)展(Extension)類加載器

擴(kuò)展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)加載并管理 <JAVA_HOME >/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中class文件對(duì)應(yīng)的Class對(duì)象。

//從系統(tǒng)屬性中獲取擴(kuò)展(Extension)類加載器加載并管理核心類庫
System.getProperty("java.ext.dirs")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext;
C:\windows\Sun\Java\lib\ext
4.3 系統(tǒng)(System)類加載器

系統(tǒng)類加載器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)加載并管理用戶類路徑(java -classpath或-Djava.class.path變量所指定的URL資源)中class文件對(duì)應(yīng)的Class對(duì)象。開發(fā)者可以直接使用系統(tǒng)類加載器。

//從系統(tǒng)屬性中獲取系統(tǒng)(System)類加載器加載并管理核心類庫
System.getProperty("java.class.path")
...省略
D:\project_alibaba\jvm-in-action\target\classes;
...省略
4.4 案例
package jvm;
import java.lang.reflect.Constructor;

/**獲取JVM類加載器 **/
public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            System.out.println(ClassLoader.getSystemClassLoader());
            System.out.println(ClassLoader.getSystemClassLoader().getParent());
            System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@45ee12a7
null

這些類加載器的工作原理是一樣的,區(qū)別是它們的加載路徑不同,也就是說 findClass 這個(gè)方法查找的路徑不同。

5 類加載(雙親委派機(jī)制)

首先檢查這個(gè)類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則交給父加載器去加載。請(qǐng)你注意,這是一個(gè)遞歸調(diào)用,也就是說子加載器持有父加載器的引用,當(dāng)一個(gè)類加載器需要加載一個(gè) Java 類時(shí),會(huì)先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索 Java 類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時(shí),才會(huì)交還給子加載器加載,直到最終交給不存在父類加載器的啟動(dòng)(Bootstrap)類加載器加載。

image
5.1 實(shí)現(xiàn)流程

雙親委派模型對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要,它的具體實(shí)現(xiàn)在java.lang.ClassLoader類loadClass()方法中。

public Class<?> loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  

protected synchronized Class<?> loadClass(String name, boolean resolve)  
        throws ClassNotFoundException {  

    // 首先判斷該類是否已經(jīng)被加載  
    Class c = findLoadedClass(name);  
    if (c == null) {  
        //如果沒有被加載,就委托給父類加載
        try {  
            if (parent != null) {  
                //如果存在父類加載器,就委派給父類加載器加載,這里是一個(gè)遞歸調(diào)用的過程。  
                c = parent.loadClass(name, false);  
            } else {    // 遞歸終止條件
                // 由于啟動(dòng)類加載器無法被Java程序直接引用,因此默認(rèn)用 null 替代
                // parent == null就意味著由啟動(dòng)類加載器嘗試加載該類,  
                // 即通過調(diào)用 native方法 findBootstrapClass0(String name)加載  
                c = findBootstrapClass0(name);  
            }  
        } catch (ClassNotFoundException e) {  
            // 如果父類加載器不能完成加載請(qǐng)求時(shí),再調(diào)用自身的findClass方法進(jìn)行類加載,若加載成功,findClass方法返回的是defineClass方法的返回值
            // 注意,若自身也加載不了,也會(huì)產(chǎn)生ClassNotFoundException異常并向上拋出
            c = findClass(name);  
        }  
    }  
    if (resolve) {  
        resolveClass(c);  
    }  
    return c;  
}
//沒錯(cuò)!此方法沒有具體實(shí)現(xiàn),只是拋了一個(gè)異常,而且訪問權(quán)限是protected。這充分證明了:這個(gè)方法就是給開發(fā)者重寫用的,即自定義類加載器時(shí)需實(shí)現(xiàn)此方法!
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
  • AppClassLoader,ExtClassLoader 對(duì)findClass實(shí)現(xiàn)都繼承自URLClassLoader,

protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        //將類的包路徑轉(zhuǎn)換為url資源路徑
                        String path = name.replace('.', '/').concat(".class");
                        //通過URLClassLoader URLClassPath ucp中獲取url路徑對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                //讀取二進(jìn)制獲取調(diào)用defineClass獲取Class對(duì)象象
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }
5.2 案例

ClassLoaderTest,EventID,Sdp屬于被不同類加載器管理的類對(duì)象,使用相同系統(tǒng)類加載器加載時(shí),由于采用雙親委派機(jī)制,獲取Class對(duì)象是由不同的類加載器加載。

@Test
    public void classLoaderLoadClass2() throws IOException {
        try {
            //調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載ClassLoaderTest
            Class typeLoaded = ClassLoaderTest.class.getClassLoader().loadClass("com.wuhao.jvm.classLoader.ClassLoaderTest");
            //查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
            System.out.println(typeLoaded.getClassLoader());

            //調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載EventID
            Class typeLoaded1 = ClassLoaderTest.class.getClassLoader().loadClass("com.sun.java.accessibility.util.EventID");
            //查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
            System.out.println(typeLoaded1.getClassLoader());

            //調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載Sdp
            Class typeLoaded2 = ClassLoaderTest.class.getClassLoader().loadClass("com.oracle.net.Sdp");
            //查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
            System.out.println(typeLoaded2.getClassLoader());
        } catch (Exception e) {
        }
    }
5.3 模式優(yōu)點(diǎn)

雙親委派模型很好的解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載).

例如類java.lang.Object,它存在在rt.jar中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。
相反,如果沒有雙親委派模型而是由各個(gè)類加載器自行加載的話,用戶編寫了一個(gè)java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,程序?qū)⒒靵y。因此,如果開發(fā)者嘗試編寫一個(gè)與rt.jar類庫中已有類重名的Java類,將會(huì)發(fā)現(xiàn)可以正常編譯,但是永遠(yuǎn)無法被加載運(yùn)行。

5.4 模式缺點(diǎn)

由于雙親委派模型存在,一個(gè)類被誰加載是由類加載器管理資源所決定,即使某個(gè)類存在于多個(gè)類加載器管理資源中。也只會(huì)被更上程的類加載器加載。這樣就會(huì)導(dǎo)致一個(gè)在被上層類加載中Class中獲取的上程類加載器,是無法加載下層類加載器中管理Class對(duì)象。

5.5 線程上下文類加載器

為了解決這個(gè)問題JVM引用了線程上下文類加載器

這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader方法進(jìn)行設(shè)置。如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過多的話,那這個(gè)類加載器默認(rèn)即使用系統(tǒng)程序類加載器。

5.6 如何破壞雙親委派模型

雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java設(shè)計(jì)者推薦給開發(fā)者的類加載器的實(shí)現(xiàn)方式。大多數(shù)的類加載器都遵循這個(gè)模型,雙親委派模型的具體邏輯實(shí)現(xiàn)在ClassLoader的loadClass方法,如果我們自定義類加載器,采用雙親委派模型:只需要重寫ClassLoader的findClass()方法即可,破壞雙親委派模型:重寫ClassLoader的整個(gè)loadClass()方法(因?yàn)殡p親委派模型的邏輯主要實(shí)現(xiàn)就在此方法中,若我們重寫即可破壞掉。)

6 Java虛擬機(jī)對(duì)類加器初始化

當(dāng)運(yùn)行一個(gè)Java程序流程:

1.根據(jù)JVM內(nèi)存配置要求,為JVM申請(qǐng)?zhí)囟ù笮〉膬?nèi)存空間;

2.創(chuàng)建一個(gè)引導(dǎo)類加載器實(shí)例,初步加載系統(tǒng)類到內(nèi)存方法區(qū)區(qū)域中;

3.創(chuàng)建JVM 啟動(dòng)器實(shí)例 Launcher,并取得構(gòu)造擴(kuò)展(Extension)類加載器,系統(tǒng)(System)類加載器;

4.使用系統(tǒng)(System)類加載器加載我們定義的 org.luanlouis.jvm.load.Main類;

5.加載完成時(shí)候JVM會(huì)執(zhí)行Main類的main方法入口,執(zhí)行Main類的main方法;

6.結(jié)束,java程序運(yùn)行結(jié)束,JVM銷毀。

6.1 初始化Launcher
public class Launcher {

 public Launcher() {
        //創(chuàng)建擴(kuò)展(Extension)類加載器
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        //創(chuàng)建系統(tǒng)(System)類加載器
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //設(shè)置AppClassLoader為線程上下文類加載器,這個(gè)文章后面部分講解
        Thread.currentThread().setContextClassLoader(this.loader);
        
    public ClassLoader getClassLoader() {
        return loader;
    }

    static class ExtClassLoader extends URLClassLoader {}


    static class AppClassLoader extends URLClassLoader {}
6.2 ExtClassLoader實(shí)例化
  • 1 讀取System.getProperty("java.ext.dirs")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組。

  • 2 調(diào)用public ExtClassLoader(File[] var1) 實(shí)例化 ExtClassLoader 擴(kuò)展類加載器。

  • 3 實(shí)例化ExtClassLoader前實(shí)例化父類URLClassLoader,實(shí)例化URLClassLoader需要將File[]轉(zhuǎn)換為URL[]作為第一個(gè)參數(shù)傳入,并傳入null來表示父類加載器
    為啟動(dòng)(Bootstrap)類加載器。

static class ExtClassLoader extends URLClassLoader {
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            //1 讀取擴(kuò)展類加載器需要加載的文件列表
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }
                        //2 實(shí)例化ExtClassLoader
                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException)var2.getException();
            }
        }

        void addExtURL(URL var1) {
            super.addURL(var1);
        }

        //3 構(gòu)造擴(kuò)展類加載器
        public ExtClassLoader(File[] var1) throws IOException {
            //3 實(shí)例化ExtClassLoader前實(shí)例化父類URLClassLoader
            //第一個(gè)參數(shù)將File[]轉(zhuǎn)換為URL[] 
            //第二個(gè)參數(shù)用來填寫父類加載器,這里父類加載器為啟動(dòng)加載器,用null表示
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }


        private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for(int var4 = 0; var4 < var3; ++var4) {
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }

            return var1;
        }


        private static URL[] getExtURLs(File[] var0) throws IOException {
            Vector var1 = new Vector();

            for(int var2 = 0; var2 < var0.length; ++var2) {
                String[] var3 = var0[var2].list();
                if (var3 != null) {
                    for(int var4 = 0; var4 < var3.length; ++var4) {
                        if (!var3[var4].equals("meta-index")) {
                            File var5 = new File(var0[var2], var3[var4]);
                            var1.add(Launcher.getFileURL(var5));
                        }
                    }
                }
            }

            URL[] var6 = new URL[var1.size()];
            var1.copyInto(var6);
            return var6;
        }
        ...省略代碼
6.3 AppClassLoader實(shí)例化

1 讀取System.getProperty("java.class.path")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組

2 將File[] var1轉(zhuǎn)換為URL[] 構(gòu)建AppClassLoader

3 構(gòu)建父類URLClassLoader 傳入父加載器var2為擴(kuò)展類加載器

 static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);


        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            1 讀取System.getProperty("java.class.path")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                   //將File[] var1轉(zhuǎn)換為URL[] 構(gòu)建AppClassLoader
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    //構(gòu)建AppClassLoader 
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

        AppClassLoader(URL[] var1, ClassLoader var2) {
            //構(gòu)建父類URLClassLoader 傳入父加載器var2為擴(kuò)展類加載器
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }
6.4 URLClassLoader 初始化
  • 1 將URL資源交給URLClassPath類型的字段管理
    public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
         1 將URL資源交給URLClassPath類型的字段管理
        ucp = new URLClassPath(urls, factory);
        acc = AccessController.getContext();
    }
6.5 ClassLoader 初始化
  • 1 設(shè)置父類加載器給屬性parent
private final ClassLoader parent;

        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    
    private ClassLoader(Void unused, ClassLoader parent) {
        //1 設(shè)置父類加載器給屬性parent
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容