一點一滴,水滴石穿
最近重溫nio與socket,把源碼看了一遍,做了一些筆記。
由於前段時間和同事討論了一下類加載器,這一次看了socket相關的源碼,看到了SPI,聯想了一下,類加載器真是無處不在,原來是這麼玩的。
起源 Selector.open
通過java的nio,做了基於socket的文件傳輸,想着netty底層實現要溫故而知新纔行。
Selector selector = Selector.open();
從open方法進去,可以看到provider
provider的作用
/**
* Opens a selector.
*
* <p> The new selector is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
* of the system-wide default {@link
* java.nio.channels.spi.SelectorProvider} object. </p>
*
* @return A new selector
*
* @throws IOException
* If an I/O error occurs
*/
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
provider的作用主要是:打開DatagramChannel、Pip、Selector、channels、ServerSocketChannel、SocketChannel。對應的是:java.nio.channels.DatagramChannel
、java.nio.channels.Pip
、java.nio.channels.Selector
、java.nio.channels.ServerSocketChannel
、java.nio.channels.SocketChannel
/**
* Returns the system-wide default selector provider for this invocation of
* the Java virtual machine.
*
<此處省略了一些>
* <p> Subsequent invocations of this method return the provider that was
* returned by the first invocation. </p>
*
* @return The system-wide default selector provider
*/
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
看到這裏就比較有意思了。可以看到,在構造provider的時候,採用了三種方式,
- 從property中加載(loadProviderFromProperty)
- 如果沒有system.setProperty的方式配置Provider,那麼就採用jar能夠加載的類路徑方式(例如ext、application)加載provider(loadProviderAsService)
- 都沒有的話,才採用默認的方式,sun.nio.ch.DefaultSelectorProvider.create()。
SelectorProvider中的類加載
private static boolean loadProviderAsService() {
ServiceLoader<SelectorProvider> sl =
ServiceLoader.load(SelectorProvider.class,
ClassLoader.getSystemClassLoader());
Iterator<SelectorProvider> i = sl.iterator();
for (;;) {
try {
if (!i.hasNext())
return false;
provider = i.next();
return true;
} catch (ServiceConfigurationError sce) {
if (sce.getCause() instanceof SecurityException) {
// Ignore the security exception, try the next provider
continue;
}
throw sce;
}
}
}
這裏我們重點看看類加載相關的,也就是loadProviderAsService,裏邊使用了一個ServiceLoader的類加載器來加載具體的SelectorProvider實現類,我們從包名 java.nio.channels.spi.SelectorProvider
和所在的rt.jar,可以瞭解到這個方法屬於java的基礎類,在1.4版本的時候就定義好了。
SystemClassLoader加載的是那些jar?
接着,可以先看看 ClassLoader.getSystemClassLoader()
, 這個方法是獲取系統的類加載器。
這裏簡單看看,首先,我們可以瞭解一下熟悉的類加載器的加載順序:boot、ext、app、自定義。
但是這裏SystemClassLoader是屬於那一層的加載呢?
在getSystemClassLoader進去可以看到有個 initSystemClassLoader
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
// <此處省略一些代碼>
}
sclSet = true;
}
}
sun.misc.Launcher.getLauncher()
, 到底用的是哪一個的類加載器呢?我們可以從Launcher的構造器一探究竟。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
// <此處省略一些代碼>
}
}
最後的loader 落在了AppClassLoader上,也就是說,只要在app以上的類加載路徑(根據boot、ext、app的順序),也只有ext與app這兩個路徑(一個是jre/ext/*路徑,一個是java.class.path)只要被加載到,就會被這個 loadProviderAsService
調用。
service的類加載器
接着我們繼續看回 ServiceLoader
/**
* Creates a new service loader for the given service type and class
* loader.
*
* @param <S> the class of the service type
*
* @param service
* The interface or abstract class representing the service
*
* @param loader
* The class loader to be used to load provider-configuration files
* and provider classes, or <tt>null</tt> if the system class
* loader (or, failing that, the bootstrap class loader) is to be
* used
*
* @return A new service loader
*/
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
SelectorProvider使用的是SystemClassLoader的load重載方法,但是這裏我也注意到了load另外一個方法
/**
* 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);
}
這裏可以看到 Thread.currentThread().getContextClassLoader()
, 這種方式有什麼用呢?
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
load被很多方法調用到,這裏我們就選用比較經常會用到的DriverManager來看看。DriverManager
來自rt.jar,是核心的基礎類,那麼如果是按照雙親委託的方式,這個Driver的實現類,如果不是存放在rt.jar能夠讀到的系統路徑上(ext、app)是加載不到的。
但是一般來說,jdbc的驅動包是放在項目下的,所以這裏需要打破雙親委託。load只有一個參數的重載方法中, Thread.currentThread().getContextClassLoader()
,就是用來打破雙親委託機制的。
它獲取的是當前線程的上下文類加載器,只要獲得上下文類加載器,那麼就可以無視雙親委託機制啦
下面分享一下深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)中關於打破雙親委託機制。