最近重溫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
執行結果: