一点一滴,水滴石穿
最近重温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版)中关于打破双亲委托机制。