public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
从上文可以看出调用了initSystemClassLoader方法。scl是一个ClassLoader对象的引用,下面是安全部分的代码,先跳过。那么我们就来看看initSystemClassLoader()方法做了什么事情。
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();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
sclSet是一个布尔类型的对象,表示scl是否被设置值。所以做了一个双重判断,sclSet为false的时候,scl应该是为空的。否则调用sun.misc.Launcher.getLauncher()方法获得Launcher的一个引用。我们待会再看这个方法。假设方法返回了一个Launcher对象,将Launcher中的ClassLoader成员变量赋值给了scl。并且在权限安全的情况下,new了一个SystemClassLoaderAction,传入了scl。最后把sclSet赋值为true。这个方法中有两个方法需要讲解,一个是sun.misc.Launcher.getLauncher()。另外一个是new SystemClassLoaderAction(scl)
我们先来看看sun.misc.Launcher.getLauncher()
……
private static Launcher launcher = new Launcher();
……
public static Launcher getLauncher() {
return 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);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
只写了一部分,大概的流程是:先获取了扩展类加载器的对象(是Launcher的静态内部类,通过加载ExtDirs文件夹下面的jar包,存为文件并进行解析),然后将获得的扩展类加载器的对象传给AppClassLoader,生成一个AppClassLoader的对象,并将其赋值给Launcher类的成员变量:loader。且将线程的上下文类加载器设置为该AppClassLoader的引用。下面是安全的代码,先跳过。
new SystemClassLoaderAction(scl))做了啥呢?
class SystemClassLoaderAction
implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;
SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent;
}
public ClassLoader run() throws Exception {
String cls = System.getProperty("java.system.class.loader");
if (cls == null) {
return parent;
}
Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}
这是一个实现了PrivilegedExceptionAction的类,所以会自动调用run方法。run方法里就是看java.system.class.loader是否给赋值,(这是我们之前自定义系统类加载器的参数还记得吗?),如果没有赋值直接返还系统类加载器(传进来的scl),否则根据带ClassLoader参数的构造方法new一个新的对象,并且将这个新new出来的对象作为当前线程的上线文类加载器。将scl作为新的系统类加载器的父加载器。
最后返回的scl即经过这些步骤后的scl
每个类都会尝试用它自己的加载器(即加载自身的加载器)来尝试加载这个类依赖的其他类.
例如ClassX引用了ClassY那么加载X的类加载器就会去加载Y(前提是Y没有被加载)
线程上下文类加载器是从JDK1.2开始引入的。类Thread中getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来设置和获取上下文类加载器。
如果没有通过setContextClassLoader(ClassLoader cl)进行设值的话,线程将继承其父线程的类加载器
Java运行时的初始线程的上下文加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源。
SPI(Service Provider Interface)
线程上下文类加载器的重要性:
父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classloader加载的类。这就改变类父ClassLoader或是其他没有父子关系的ClassLoader加载类的情况,即改变了双亲委托模型。
在双亲委托模型下,类加载器是由下至上的,但是对于SPI来说,有些接口是由Java核心库提供的,而Java核心库是由启动类加载器加载的,而这些接口的实现却来自于不同的jar包(厂商),Java的启动类加载器是不会加载其他来源的jar包的,这样传统的双亲委托机制就不满足需求。而通过当前线程设制相应的上下文加载器,就可以由设置的上下文类加载器来实现对接口实现类的加载
public class MyTest26 {
public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> it = loader.iterator();
while (it.hasNext()) {
Driver driver = it.next();
System.out.println("class: " + driver.getClass() + ", classLoader: " + driver.getClass().getClassLoader());
}
System.out.println("线程上下文类加载器:" + Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader的类加载器:" + ServiceLoader.class.getClassLoader());
}
}
结果:
class: class com.mysql.jdbc.Driver, classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
class: class com.mysql.fabric.jdbc.FabricMySQLDriver, classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
线程上下文类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器:null
我们来看一下ServiceLoader的Document(太长了。。下次复习自己再翻译一遍。。不想写出来献丑了)
针对于ServiceLoader.load(Driver.class);源代码中的一些重要部分做一个讲解:
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
将当前线程的类加载器赋值给了重载的load方法。
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
生成了一个ServiceLoader的对象。
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
将当前线程的上下文加载器赋给了loader,如果是空的则给系统类加载器。将已经加载过了的服务提供者(providers)进行清空。new一个延迟加载的对象,将服务接口和加载器都传入进去。
至此,load方法执行结束了,一些初始化过程已经完成了。
接下来我们来分析下面的代码:
下面是iterator的代码:
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
返回的是new的一个新的Interator。生成providers的一个迭代器。providers是服务提供者的map。重写了hasNext()方法和next()、remove()方法.
接下来我们一个个来分析:
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
hasNextService()刚刚分析过,看是否有下一个元素,如果没有直接抛出异常了,cn为nextName。即在hasNextService方法最后将name赋值为pending.next()的一个元素。也就是META-INF/services/java.sql.Driver文件中的一个二进制名字。然后调用Class.forName方法,就之前ServiceLoader.load()方法赋值的loader来加载这个二进制类名,并且初始化它。将初始化完成的对象放入providers中(key为二进制名字,value为实例号的对象,并将这个对象返回)。
至此,我们拿到了Driver服务的一个服务提供者对象。并且打印出来
下面两行代码发生了什么事情?
public class MyTest27 {
public static void main(String[] args) throws Exception{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc://mysql://localhost", "username", "password");
}
}
一开始看的太浅了。就只看了forName方法和getConnection方法。这个作用的其一重要点在于:Class.forName会导致被加载的二进制名字的类初始化。且,调用某个类的静态方法,也会导致直接定义这个静态方法的类的初始化。
来分析源码吧,分两个语句就用两个标题
Reflection.getCallerClass();返回调用这个方法所在的类的对象,在这里也就是MyTest27的Class对象。用MyTest27的类加载器加载给定的二进制名字,并且初始化这个二进制名字所代表的类。
那么我们就需要看Driver类的初始化会导致什么样的代码执行
会执行这部分静态代码块。由于对DriverManager类中的静态方法的主动调用,会导致DriverManager类的初始化,会先初始化DriverManager类,在调用registerDriver方法。我们先看看DriverManager类初始化完成了什么样的工作:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
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.ServiceConfigurationError
* 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);
}
}
}
分析一下loadInitialDrivers方法。一开始的jdbc.drivers为空(可以手动自己运行一下看看),第二个全线中的RUN方法就是我们上一节讲解的源代码。将META-INF/services/java.sql.Driver文件下的所有二进制名字加载且初始化放入providers容器中。然后就返回了,因为drivers为空。
再返还到registerDriver方法,将自身注册进registeredDrivers去(我感觉这边addIfAbsent方法会将driver下的所有实现了的方法进行一个递归添加,所以Driver服务下有几个服务提供者就会加多少次):
接下来在进行连接等操作。