ClassLoader雙親委派機制在Java SPI中的應用

概述

Java SPI機制指的是java來定義接口,然後由不同的廠商去實現這個接口,比如數據庫的驅動程序就是由不同廠商實現的,MySQL的驅動和Oracle的驅動是不同的,但是它們都實現了java.sql.Driver接口。下面就來探索程序中如何去獲取這個具體實現以及類加載器的雙親委派機制在其中的作用。

Java SPI示例

1、實現服務

假設java.lang.Runnable是一個服務,需要各個廠商去實現這個服務,這裏有廠商A和廠商B,廠商A的實現如下

package cn.cjc.hh;

/**
 * @author debo
 * @date 2020-01-28
 */
public class HanHongSpiService implements Runnable {
    @Override
    public void run() {
        System.out.println("韓紅開始唱歌了...");
    }
}

廠商A在實現這個接口後,將這個實現類打成jar包,同時在jar包中還有一個文件夾,名字爲META-INF/services,裏面有個文件,名字爲java.lang.Runnable,該文件中只有一行內容,是cn.cjc.hh.HanHongSpiService,如圖所示:
在這裏插入圖片描述
廠商B的實現如下

package cn.cjc.sn;

/**
 * @author debo
 * @date 2020-01-28
 */
public class SunNanSpiService implements Runnable {
    @Override
    public void run() {
        System.out.println("孫楠開始唱歌了...");
    }
}

廠商B在實現這個接口後,將這個實現類打成jar包,同時在jar包中還有一個文件夾,名字爲META-INF/services,裏面有個文件,名字爲java.lang.Runnable,該文件中只有一行內容,是cn.cjc.sn.SunNanSpiService

2、使用服務

使用服務的代碼如下:

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @author debo
 * @date 2020-01-28
 */
public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<Runnable> services = ServiceLoader.load(Runnable.class);
        Iterator<Runnable> iterator = services.iterator();
        while (iterator.hasNext()) {
            Runnable service = iterator.next();
            System.out.println(service.getClass());
            System.out.println(service.getClass().getClassLoader());
            service.run();
        }
    }
}

輸出如下:

class cn.cjc.hh.HanHongSpiService
sun.misc.Launcher$AppClassLoader@420a52f
韓紅開始唱歌了...
class cn.cjc.sn.SunNanSpiService
sun.misc.Launcher$AppClassLoader@420a52f
孫楠開始唱歌了...

3、ClassLoader 雙親委派機制的作用

由輸出結果可知,接口的實現類都由 AppClassLoader 加載的,而接口 Runnable 肯定由根類加載器加載,那麼接口和實現類由不同的類加載器加載是怎麼做到的呢?答案在 ServiceLoader.load() 方法中,此方法的源碼如下

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

可以知道,加載實現類的類加載器用的是當前線程上綁定的類加載器,如果你沒有給當前線程設置自定義的類加載器,那麼類加載器默認將會是 AppClassLoader, AppClassLoader 先委託它的上級,也就是ExtClassLoader來加載接口實現類,ExtClassLoader 會委託它的上級,也就是根類加載器來做這個事。無論是 ExtClassLoader 還是根類加載器都無法找到接口的實現類,所以最終接口的實現類由 AppClassLoader 加載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章