java用ClassLoader表示一个类加载器,所有其它类加载器都继承至ClassLoader。下面通阅读ClassLoader源码,来了解ClassLoader的实现原理。
/**ClassLoader成员变量parent,父加载器**/
privatefinal ClassLoader parent;
/**ClassLoader成员变量scl,其实就是SystemClassLoader**/
privae static ClassLoader scl;
/**构造方法**/
protected ClassLoader(ClassLoader parent){
/**checkCreateClassLoader方法只做了安全检查**/
/**this调用了另外一个构造函数**/
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent){
/**也是传入了父加载器,并赋值给了parent成员变量**/
this.parent = parent;
/**。。。省去了一部分代码**/
}
从构造方法可以看出,构造一个类加载器,需要传入一个父加载器。从成员变量parent及构造方法可以看出,父加载器中的“父”是逻辑上意义,不是extends关系。
从上面的代码可以看出,构造类加载器需要指定父加载器。那么问题来了,如果调用的是无参构造器,那父加载器是谁呢?继续看代码。
/**无参构造器**/
protected ClassLoader(){
this(checkCreateClassLoader(), getSystemClassLoader());
/**
通过代码可以看出,无参构造器的父加载器是getSystemClassLoader()的返回值,
其实他的返回值就是AppClassLoader,也叫SystemClassLoader,后面会介绍getSystemClassLoader()方法。这就是为什么我们自己定义的加载器,如果没有指定父加载器,默认的父加载器就是AppClassLoader的原因。
*/
}
下面看下getSystemClassLoader()方法。
publicstatic ClassLoader getSystemClassLoader(){
/**又调用了initSystemClassLoader()方法**/
initSystemClassLoader();
/**scl是一个ClassLoader的成员变量,上面有提到**/
if(scl ==null){
returnnull;
}
/**。。。省去了一部分代码。
返回scl,就是SystemClassLoader,下面会提到
**/
return scl;
}
getSystemClassLoader()方法又调用的initSystemClassLoader()方法。
privatestaticsynchronizedvoid initSystemClassLoader(){
if(!sclSet){
if(scl !=null)
thrownew IllegalStateException("recursive invocation");
/**获取Launcher类**/
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if(l !=null){
Throwable oops =null;
/**从Launcher类中获取ClassLoader,并赋值给了scl,
其实这个scl就是AppClassLoader(后面会讲到)
*/
scl = l.getClassLoader();
}
/**…省去了一些代码**/
}
从以上代码可以看出,我们自己定义的加载器,如果没有指定父加载器,默认的父加载器就是AppClassLoader。那么类加载器又是如何加载类的呢?
类加载器加载类的时候调用加载器的loadClass方法。
protected Class> loadClass(String name,boolean resolve)
throws ClassNotFoundException{
synchronized(getClassLoadingLock(name)){
/**首先查看这个类是否被加载,加载的类都会被缓存**/
Class> c = findLoadedClass(name);
/**没有加载过的类才会被加载**/
if(c ==null){
long t0 = System.nanoTime();
try{
/**首先查看父加载器是否为空,不为空就委托给父加载器去加载**/
if(parent !=null){
/**委托给父加载器加载**/
c = parent.loadClass(name,false);
}else{
/**如果父加载器为空,就委托BootstrapClassLoader去加载,
自定义的加载器父加载器默认是AppClassLoader,上面已经说过了
*/
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
}
/**如果父加载器及BootstrapClassLoader都没加载到**/
if(c ==null){
long t1 = System.nanoTime();
/**如果父加载器及BootstrapClassLoader都没加载到才调用自己的findClass方法去加载这个类,下面会分析findClass方法
*/
c = findClass(name);
/**。。。省去一部分代码**/
}
}
if(resolve){
resolveClass(c);
}
return c;
}
}
从上面可以出类的加载机制为委托父类加载机制:先递归调用父加载器的loadClass方法,一直到BootstrapClassLoader为止,再调用自己findClass方法去加载。
下面看ClassLoader的findClass方法。
protected Class> findClass(String name)throwsClassNotFoundException{
thrownew ClassNotFoundException(name);
}
findClass方法是ClassLoader的一个空实现,留给子类去实现,不同的子类可能有不同的实现。不如看一下最常用的URLClassLoader是怎样实现这个方法的。
URLClassLoader的成员变量:
privatefinal URLClassPath ucp; /**用于加载资源**/
URLClassLoader的构造方法:
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactoryfactory){
super(parent);
SecurityManager security =System.getSecurityManager();
if(security !=null){
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp =new URLClassPath(urls, factory, acc);
}
从构造方法可以看出,要构造URLClassLoader,也必须传入一个父加载器,同上面的结论一致。
URLClassLoader的findClass方法:
protected Class> findClass(final String name)
throws ClassNotFoundException{
final Class> result;
try{
result = AccessController.doPrivileged(
new PrivilegedExceptionAction>(){
public Class> run()throws ClassNotFoundException {
/**把类的.替换成文件路径的**/
String path = name.replace('.','/').concat(".class");
/**ucp是一个URLClassPath类型的成员变量,
通过这行代码可以看出,加载类也是通过加载资源的方式去实现的
*/
Resource res = ucp.getResource(path,false);
if(res !=null){
try{
/**加载到资源后,把资源转换成Class**/
return defineClass(name, res);
}catch(IOException e){
thrownew ClassNotFoundException(name, e);
}
}else{
returnnull;
}
}
}, acc);
}catch(java.security.PrivilegedActionException pae){
throw(ClassNotFoundException) pae.getException();
}
if(result ==null){
thrownew ClassNotFoundException(name);
}
return result;
}
直接看URLClassPath是怎样加载资源的。
/**var1是类的路径**/
Resource getResource(final String var1,boolean var2){
final URL var3;
try{
/**把类的路径转成URL**/
var3 =new URL(this.base, ParseUtil.encodePath(var1,false));
}catch(MalformedURLException var7){
thrownew IllegalArgumentException("name");
}
final URLConnection var4;
try{
if(var2){
URLClassPath.check(var3);
}
/**通过URL的openConnection打开一个连接**/
var4 = var3.openConnection();
/**获取文件的输入流**/
InputStream var5 = var4.getInputStream();
/**。。。省去部分代码**/
/**然后返回一个带有输入流的Resource**/
returnnew Resource(){
public String getName(){
return var1;
}
public URL getURL(){
return var3;
}
public URL getCodeSourceURL(){
return Loader.this.base;
}
public InputStream getInputStream()throws IOException {
return var4.getInputStream();
}
publicint getContentLength()throws IOException {
return var4.getContentLength();
}
};
}
从以上代码可以看出,java加载类是通过加载资源的方式是实现的,即打开一个文件输入流,包装成Resource,然后通过调用defineClass,把输入流转成一个Class文件。defineClass最后调用一个native的本地方法把输入流转成class,就不贴代码了。
上面讲完了类的加载机制,下面看下BootStrapClassLoader,ExtClassLoader,AppClassLoader的父子关系是何时建立起来的。
java虚拟机在启动好之后会调用一个java类:sun.misc.Launcher,但这个类是不开源的,可以通过intellijidea等这类ide工具反编译出其源码。下面就通过阅读这个类来回答上面的问题。
publicclass Launcher {
/**这个就是我们熟知的AppClassLoader, 后面的代码中用会到**/
private ClassLoader loader;
public Launcher(){
Launcher.ExtClassLoader var1;
try{
/**A:建立ExtClassLoader**/
var1 = Launcher.ExtClassLoader.getExtClassLoader();
}catch(IOException var10){
thrownew InternalError("Could not create extension classloader", var10);
}
try{
/**B:传入参数var1,也就是A步骤建立的ExtClassLoader,用于建立AppClassLoader**/
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}catch(IOException var9){
thrownew InternalError("Could not create application classloader", var9);
}
/**
这行代码很重要,这行代码的作用是设置当前上下文加载器为AppClassLoader,这也就是为什么在我们的应用中获取类加载器的时候总是获取到AppClassLoader,这行代码是意义是变换了当前上下文类加载器,使顶层ClassLoader可以加载底层ClassLoader的类。
*/
Thread.currentThread().setContextClassLoader(this.loader);
/**...去掉了一些代码**/
}
}
通过阅读Launcher类的构造函数,我们知道了java建立了两个类加载器,AppClassLoader,ExtClassLoader。并设置上下文加载器为AppClassLoader。上下文加载器突破java的类加载机制,用于解决顶层ClassLoader无法访问底层ClassLoader的类的问题。但对于他们的关系,以及他们能加载到的资源还未涉及到。
下面来看下ExtClassLoader是如何建立起来的。
ExtClassLoader是Launcher的静态内部类。父类是URLClassLoader。
staticclass ExtClassLoader extends URLClassLoader {
publicstatic Launcher.ExtClassLoader getExtClassLoader()throws IOException{
/**C:var0定义了要从何处去加载资源。这是要从extdirs中去加载,extdirs后续会讲到。*/
final File[] var0 = getExtDirs();
try{
return(Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(){
public Launcher.ExtClassLoader run()throws IOException {
int var1 = var0.length;
for(int var2 =0; var2 < var1;++var2){
MetaIndex.registerDirectory(var0[var2]);
}
/**
D:调用了Launcher.ExtClassLoader,入参是var0,也就上C步骤定义的加载资源的地方
*/
returnnew Launcher.ExtClassLoader(var0);
}
});
}catch(PrivilegedActionException var2){
throw(IOException)var2.getException();
}
}
/**E:ExtClassLoader构造方法入参是D步骤的var0,也就是extdirs**/
public ExtClassLoader(File[] var1)throws IOException {
/**
此处调用了getExtURLs方法。getExtURLs方法把var1目录中的子目录或者文件转成url。此处调用了父类的构造器,ExtClassLoader的父类是URLClassLoader,看下URLClassLoader的构造方法里面做了什么。
super调用了URLClassLoader的构造方法,但此处传入的parent加载器却是null,其实这个null就是BootstrapClassLoader,因BootstrapClassLoader为c++所写,所以用java是获取不到这个加载器的,也就是null。
*/
super(getExtURLs(var1),(ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
/**...省去了一些代码**/
}
从代码中可以看出
ExtClassLoader
的父加载器为
null.
其实就是
BootStrapClassLoader
了,就好比
String
的
classLoader
为
null
一样。
ExtClassLoader加载器通过getExtDirs方法返回可以加载的资源路径。
privatestatic File[] getExtDirs(){
/**java.ext.dirs定义了ExtClassLoader加载资源的路径**/
String var0 =System.getProperty("java.ext.dirs");
File[] var1;
if(var0 !=null){
StringTokenizer var2 =new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 =new File[var3];
for(int var4 =0; var4 < var3;++var4){
var1[var4]=new File(var2.nextToken());
}
}else{
var1 =new File[0];
}
return var1;
}
下面看AppClassLoader。
staticclass AppClassLoader extends URLClassLoader {
publicstatic ClassLoader getAppClassLoader(final ClassLoader var0)throws IOException {
/**java.class.path系统属性定义了AppClassLoader的加载资源路径**/
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 ==null?new File[0]:Launcher.getClassPath(var1);
return(ClassLoader)AccessController.doPrivileged(new PrivilegedAction(){
public Launcher.AppClassLoader run(){
URL[] var1x = var1 ==null?new URL[0]:Launcher.pathToURLs(var2);
/**
返回AppClassLoader,var1x 为加载路径的url
var0为父加载器
*/
returnnew Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2){
/**
调用的super为URLClassLoader,通过前面对URLClassLoader
的分析知道,var2为父加载器
*/
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
通过分析Launcher的源码,可以看出AppClassLoader的父加载器为ExtClassLoader,AppClassLoader从java.class.path系统属性定义的路径中加载类和资源,java.class.path默认为当前路径,也就是”.”。可参考文档:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html。当我们用ide开发的时候,默认的类路径就是当前工程所在的文件夹,可那里并没有我们要加载的类,所以ide 会重写类路径。Eclipse会把类路径记录在.classpath文件中。Intellij IDEA 会记录在.iml文件中。ExtClassLoader的父加载器为BootstrapClassLoader,从java.ext.dirs系统属性定义的路径中加载类和资源。