一。類加載器及雙親委派機制
1. 類加載器繼承結構
2. 類加載器的核心方法
?3. Launcher 類源碼解析
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher();
// 啟動類加載器加載路徑
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
// 獲取擴展類加載器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the Application
try {
// 獲取應用類加載器
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// Also set the context class loader for the primordial thread.
// 設置線程上下文類加載器為應用類加載器
Thread.currentThread().setContextClassLoader(loader);
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {
private static volatile ExtClassLoader instance = null;
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
if (instance == null) {
synchronized(ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
/**
* 獲取加載路徑
*/
private static File[] getExtDirs() {
// 擴展類加載器加載路徑
String s = System.getProperty("JAVA.ext.dirs");
}
}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
// 應用類加載器加載路徑
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
}
?4. ClassLoader 類源碼解析
public abstract class ClassLoader {
protected 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 (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(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;
}
}
// 自定義類加載器需要重寫該方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
5. 雙親委派機制優缺點
優點:
1、保證安全性,層級關系代表優先級,也就是所有類的加載,優先給啟動類加載器,這樣就保證了核心類庫類
2、避免類的重復加載,如果父類加載器加載過了,子類加載器就沒有必要再去加載了,確保一個類的全局唯一性
缺點:
檢查類是否加載的委派過程是單向的, 這個方式雖然從結構上說比較清晰,使各個 ClassLoader 的職責非常明確, 但是同時會帶來一個問題, 即頂層的 ClassLoader 無法訪問底層的 ClassLoader 所加載的類
通常情況下, 啟動類加載器中的類為系統核心類, 包括一些重要的系統接口,而在應用類加載器中, 為應用類。 按照這種模式, 應用類訪問系統類自然是沒有問題, 但是系統類訪問應用類就會出現問題。
二.spi 接口及線程上下文類加載器
1.spi 接口定義及線程上下文加載的作用
Java 提供了很多核心接口的定義,這些接口被稱為 SPI 接口。(Service Provider Interface,SPI),允許第三方為這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現代碼則是作為 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)里。SPI 接口中的代碼經常需要加載具體的實現類。那么問題來了,SPI 的接口是 Java 核心庫的一部分,是由啟動類加載器 (Bootstrap Classloader) 來加載的;SPI 的實現類是由系統類加載器 (System ClassLoader) 來加載的。引導類加載器是無法找到 SPI 的實現類的,因為依照雙親委派模型,BootstrapClassloader 無法委派 AppClassLoader 來加載類。而線程上下文類加載器破壞了 “雙親委派模型”,可以在執行線程中拋棄雙親委派加載鏈模式,使程序可以逆向使用類加載器。
類加載傳導規則:JVM 會選擇當前類的類加載器來加載所有該類的引用的類。例如我們定義了 TestA 和 TestB 兩個類,TestA 會引用 TestB,只要我們使用自定義的類加載器加載 TestA,那么在運行時,當 TestA 調用到 TestB 的時候,
TestB 也會被 JVM 使用 TestA 的類加載器加載。依此類推,只要是 TestA 及其引用類關聯的所有 jar 包的類都會被自定義類加載器加載。通過這種方式,我們只要讓模塊的 mAIn 方法類使用不同的類加載器加載,那么每個模塊的都會使用 main
方法類的類加載器加載的,這樣就能讓多個模塊分別使用不同類加載器。這也是 OSGi 和 SofaArk 能夠實現類隔離的核心原理。
2. spi 加載原理
當第三方實現者提供了服務接口的一種實現之后,在 jar 包的 META-INF/services/ 目錄里同時創建一個以服務接口命名的文件,該文件就是實現該服務接口的實現類。而當外部程序裝配這個模塊的時候,就能通過該 jar 包 META-INF/services/ 里的配置文件找到具體的實現類名,并裝載實例化,完成模塊的注入。
JDK 官方提供了一個查找服務實現者的工具類:java.util.ServiceLoader
public final class ServiceLoader<S>
implements Iterable<S>
{
// 加載spi接口實現類配置文件固定路徑
private static final String PREFIX = "META-INF/services/";
/**
* Creates a new service loader for the given service type, using the
* current thread's {@linkplain java.lang.Thread#getContextClassLoader
* context class loader}.
* <p> An invocation of this convenience method of the form
* <blockquote><pre>
* ServiceLoader.load(<i>service</i>)</pre></blockquote>
* is equivalent to
* <blockquote><pre>
* ServiceLoader.load(<i>service</i>,
* Thread.currentThread().getContextClassLoader())</pre></blockquote>
* @param <S> the class of the service type
* @param service
* The interface or abstract class representing the service
* @return A new service loader
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
// 線程上下文類加載器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
?3. 示列代碼
代碼:
public interface IShout {
void shout();
}
public class Dog implements IShout {
@Override
public void shout() {
System.out.println("wang wang");
}
}
public class Cat implements IShout {
@Override
public void shout() {
System.out.println("miao miao");
}
}
public class Main {
public static void main(String[] args) {
ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
for (IShout s : shouts) {
s.shout();
}
}
}
配置:
??4.MySQL 驅動類加載
// 加載Class到AppClassLoader(系統類加載器),然后注冊驅動類
//Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
// 通過java庫獲取數據庫連接
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
。。。。。。。
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurati
* will be thrown at runtime by the VM trying to locate
* and load the service.
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}
三。自定義動態類加載器
1. 示例代碼
public class DynamicClassLoad extends ClassLoader{
public static void main(String[] args) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
DynamicClassLoad myClassLoad = new DynamicClassLoad();
Class clazz = myClassLoad.findClass("/Users/wangzhaoqing1/Desktop/MyTest.class");
Object obj = clazz.newInstance();
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.invoke(obj, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}, 1, 2, TimeUnit.SECONDS);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File(name);
try {
byte[] bytes = FileUtils.readFileToByteArray(file);
Class<?> c = this.defineClass(null, bytes, 0, bytes.length);
return c;
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
// DynamicClassLoad啟動后,修改本類重新編譯
public class MyTest {
public void sayHello(){
System.out.println("hello wzq 6666666666");
}
}
作者:京東零售 王照清
來源:京東云開發者社區 轉載請注明來源