類加載機制及SPI


最近重溫Java類加載及雙親委派機制,並寫了一個SPI的例子

 

 

 

 

從網上找了一張圖片,對着圖片及課堂筆記來梳理下。

首先java自帶的類加載器分為BootStrapClassLoader(引導\啟動類加載器),ExtClassLoader(擴展類加載器),AppClassLoader(應用程序類加載器)三種,此外還支持用戶自己定義的自定義類加載器,加載的是用戶自己指定的目錄。

BootStrapClassLoader:jvm中,c++處理類加載的這套邏輯,被稱為啟動類加載器,是由c++編寫的,在java中為null,加載的路徑是Jre/lib/rt.jar, 在這個過程中會通過啟動類加載器,來加載sun.launcher.LauncherHelper,並執行checkAndLoadMain,以及加載main函數所在的類,並啟動擴展類加載器、應用類加載器

 

ExtClassLoader: 擴展類加載器,加載的是Jre/lib/ext/*.jar,查看方式:

    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

        URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

        URL[] urls = urlClassLoader.getURLs();
        for (URL url : urls) {
            System.out.println(url);
        }
    }

 

AppClassLoader: 應用類加載器,加載用戶程序的類加載器,加載的是CLASS_PATH中指定的所有jar

    public static void main(String[] args) {
        String[] urls = System.getProperty("java.class.path").split(":");

        for (String url : urls) {
            System.out.println(url);
        }

        System.out.println("---------------------------------------------------------");

        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

        URL[] urls1 = classLoader.getURLs();
        for (URL url : urls1) {
            System.out.println(url);
        }
    }

 

 

雙親委派機制:類加載時,AppClassLoader 會先查看自身是否已經加載過當前class文件,如果加載過則直接返回,如果沒有加載過,則委托他的父類(ExtClassLoader)嘗試進行加載,ExtClassLoader也會先查看自己是否加載過,加載過則直接返回,沒有加載過,則繼續委派給BootStrapClassLoader,如果直至BootStrapClassLoader都沒有加載過,則會AppClassLoader會嘗試進行加載。

 

打破雙親委派的方式:改變這個加載流程,不向上委派

package com.learn;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {

                    if (name.startsWith("com.learn")) { // 打破雙親委派
                        c = findClass(name);
                    } else {
                        c = this.getParent().loadClass(name);
                    }

                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                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();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        return null;
    }
}

 

 

SPI:一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。這一機制為很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制

 

 

 

 

代碼如下:

首先定義一個公共接口

package com.learn.spi.common;

public interface IJdbc {

    void connection();
}

 

然后是兩個實現類

package com.learn.spi.mysql;

import com.learn.spi.common.IJdbc;

public class MysqlJdbc implements IJdbc {
    @Override
    public void connection() {
        System.out.println("this is MysqlJdbc...");
    }
}
package com.learn.spi.oracle;

import com.learn.spi.common.IJdbc;

public class OracleJdbc implements IJdbc {
    @Override
    public void connection() {
        System.out.println("this is OracleJdbc...");
    }
}

 

最后main函數使用ServiceLoader調用

package com.learn.spi.gateway;

import com.learn.spi.common.IJdbc;

import java.util.ServiceLoader;

public class GateWayMain {

    public static void main(String[] args) {

        ServiceLoader<IJdbc> iPays = ServiceLoader.load(IJdbc.class);
        for (IJdbc iJdbc : iPays) {
            iJdbc.connection();
        }
        System.out.println("end...");
    }

}

 

META-INF/services文件夾下的文件名定義為接口全路徑名,文件內容為實現類全路徑名,這里是JDK源碼中規定死的

com.learn.spi.oracle.OracleJdbc
com.learn.spi.mysql.MysqlJdbc

 

 

 

 

執行結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM