类加载器 - 自定义系统类加载器及线程上下文类加载器
自定义系统类加载器
ClassLoader.getSystemClassLoader()方法详解
方法说明
返回用于委托的系统类加载器,它是新建ClassLoader实例的默认的委托双亲,通常也是启动应用的类加载器。
这个方法在运行启动期间很早的时候就被调用,在调用时首先会创建系统加载器,而且会将其设置为调用该线程的上下文类加载器。
默认的系统类加载器是与这个类的实现相关的一个实例。
如果系统属性java.system.class.loader被定义了,这个属性的值就将做为返回的类加载器的名字。这个类是使用默认的系统类加载器所加载的,且必须要定义一个默认的参数为ClassLoader的构造方法,所生成的类加载器就被定义为新的系统类加载器。
如果安全管理器存在,并且调用者的类加载器是不是
null
和调用者的类加载器是不一样的,或者系统类加载器的祖先,则此方法调用安全管理器的checkPermission方法与RuntimePermission("getClassLoader")权限验证访问到系统类加载器。 如果没有,SecurityException
将被抛出。
源码解析
// 系统类加载器
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;
// 设置系统类加载器后,将其设置为true
// @GuardedBy("ClassLoader.class")
private static boolean sclSet;
/**
* getSystemClassLoader方法源码
*/
@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");
// Launcher为系统类加载器和拓展类加载器的一个包装,代码并不开源
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
// 将Launcher默认的系统类加载器赋给了scl
scl = l.getClassLoader();
try {
// 获取到系统类加载器,可能是系统默认的AppClassLOader,有可能是用户自定义的系统类加载器
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;
}
}
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");
// 如果系统属性为空,系统类加载器就是默认的AppClassLoader
if (cls == null) {
return parent;
}
//如果系统属性不为空,获取cls所对应的class的构造方法对象
Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
// 创建实例
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
// 将sys设置为当前线程的上下文类加载器
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}
自定义系统类加载器
首先,沿用前文中的自定义类加载器ClassLoaderTest,添加一个构造方法:
public ClassLoaderTest(ClassLoader classLoader) {
super(classLoader);
}
PS:为什么要添加这样一个构造方法:
在上文的代码解析中其实给出了答案,在设置了自定义系统类加载器,通过反射的方法获取自定义系统类加载器的Constructor对象时,需要调用到该构造方法。
Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
然后,编写一个测试类
public class Test21 {
public static void main(String[] args) {
System.out.println(System.getProperty("java.system.class.loader"));
}
}
编译该类,通过java指令设置java.system.class.loader
的值运行该类
java -Djava.system.class.loader=classloader.ClassLoaderTest classloader.Test21
运行结果如下
线程上下文类类加载器
当前类加载器
当前类加载器就是加载当前类的类加载器,每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是所依赖的类),如果Class A引用Class B,那么Class A的类加载器就会去加载Class B(前提是Class B尚未被加载)。
线程上下文类加载器
线程上下文类加载器的概念
线程上下文类加载器就是当前线程的Current ClassLoader。
JDK1.2开始引入,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)进行设置,线程将继承其父线程的上下文类加载器。
Java应用运行时的初始线程的上下文类加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源。
线程上下文类加载器的重要性
父类加载器可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的类加载器所加载的类。
这就改变了父类加载器不能使用子类加载器或是其他没有直接父子关系的类加载器所加载的类的情况,即改变了双亲委托模型。
在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI(Service Provider Interface)来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器来加载的,而这些接口的实现却来自与不同的jar包(厂商提供),Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型无法满足SPI的需求。通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。
当高层提供了统一的接口让低层去实现,同时又要在高层加载(或实例化)低层的类时,就必须要通过线程上下文类加载来帮助高层的ClassLoader找到并加载该类。
线程上下文类加载器的使用
线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(customizeClassLoader);
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}