类加载器

来源:互联网 发布:linux如何自建pdnsd 编辑:程序博客网 时间:2024/04/30 01:30

这是第一篇博客,如果您在阅读中发现错误,欢迎指出,我会吸收改正。

转载请注明http://blog.csdn.net/HEL_WOR/article/details/50103363

——————————————————————————————————
Update:类加载器内容已重写,这篇写的不好,建议移步至新博客:理解类加载器
——————————————————————————————————

C#和JAVA带来的平台无关性, 是通过中间语言来完成的,C#中的IL进入CLR,Java中的class文件进入VM,调用相关API,完成这两种语言的平台无关性,底层本地方法(native methods)和其相关的运行库带来的平台相关性由CLR和VM的API隐藏,如果你使用的语言直接调用了底层本地方法,这种语言上的平台无关也会消失。

进入VM的是class文件,二进制,严格的格式,所以VM可以按照固定的格式读取出类信息,class文件不一定必须要是文件的形式,也可以是流和其他格式。

VM将读取的class字节码转换为java.lang.class的一个实例。来看看ClassLoaer的源码:

protected Class<?> loadClass(String paramString, boolean paramBoolean) throws ClassNotFoundException {        synchronized (getClassLoadingLock(paramString)) {        /// 判断这段class文件是否已被加载 True:实例 False:null            Class localClass = findLoadedClass(paramString);            if (localClass == null) {                long l1 = System.nanoTime();                try {                /// 由父类加载                    if (this.parent != null)                        localClass = this.parent.loadClass(paramString, false);                    else                    /// 由引导类加载                        localClass = findBootstrapClassOrNull(paramString);                } catch (ClassNotFoundException localClassNotFoundException) {                }                /// 最后才用自定义类加载器加载                if (localClass == null) {                    long l2 = System.nanoTime();                    localClass = findClass(paramString);                    PerfCounter.getParentDelegationTime().addTime(l2 - l1);                    PerfCounter.getFindClassTime().addElapsedTimeFrom(l2);                    PerfCounter.getFindClasses().increment();                }            }            if (paramBoolean)                resolveClass(localClass);            return localClass;        }    }

上面的代码很多博客都已经提到过了,其也是双亲委派机制的实现,现在这里要讲的是关于

localClass = this.parent.loadClass(paramString, false);

localClass = findClass(paramString);

this.parent.loadClass(paramString, false);要想被运行到,则只可能是一种情况,即this表示的系统类(也称应用类)加载器,其parent指的是扩展类加载器。
关于为什么只有这一种情况, 看看下面这段源码:

/// 调用getLauncher()即会完成对launcher的构造。public static Launcher getLauncher() {        return launcher;    }    public Launcher() {        ///扩展类加载器        ExtClassLoader localExtClassLoader;        try {            /// 获取类加载器            localExtClassLoader = ExtClassLoader.getExtClassLoader();        } catch (IOException localIOException1) {            throw new InternalError("Could not create extension class loader", localIOException1);        }        try {            /// 获取系统类(应用类加载器)注意参数是扩展类            this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);        } catch (IOException localIOException2) {            throw new InternalError("Could not create application class loader", localIOException2);        }/// 设置线程上下文类加载器 这里是应用类加载器作为默认类加载器 在应用程序中如果你不设置上下文加载器,则默认加载器为应用类加载器。Thread.currentThread().setContextClassLoader(this.loader);        String str = System.getProperty("java.security.manager");        if (str == null)            return;        SecurityManager localSecurityManager = null;        if (("".equals(str)) || ("default".equals(str)))            localSecurityManager = new SecurityManager();        else            try {                localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();            } catch (IllegalAccessException localIllegalAccessException) {            } catch (InstantiationException localInstantiationException) {            } catch (ClassNotFoundException localClassNotFoundException) {            } catch (ClassCastException localClassCastException) {            }        if (localSecurityManager != null)            System.setSecurityManager(localSecurityManager);        else            throw new InternalError("Could not create SecurityManager: " + str);    }

Launcher是由引导类(启动类)加载器调用的并由此加载了扩展类加载器和系统类(应用类)加载器,但是不是由此就说引导类(启动类)加载器是扩展类加载器和系统类加载器的Parent?好像有点搞混了,至少父类和子类不是根据谁实现了谁来定义的,那么看看下面这段代码就可以看到这三者之间的关系了:

static class ExtClassLoader extends URLClassLoader {    public static ExtClassLoader getExtClassLoader() throws IOException {        /// 获取类加载路径        File[] arrayOfFile = getExtDirs();        try {            return ((ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {                public Launcher.ExtClassLoader run() throws IOException {                    int i = this.val$dirs.length;                    for (int j = 0; j < i; ++j)                        MetaIndex.registerDirectory(this.val$dirs[j]);                    return new Launcher.ExtClassLoader(this.val$dirs);                }            }));        } catch (PrivilegedActionException localPrivilegedActionException) {            throw ((IOException) localPrivilegedActionException.getException());        }    }
private static File[] getExtDirs() {/// 这一段代码即是扩展类默认的加载路径(<Java_Home\lib\ext>)            String str = System.getProperty("java.ext.dirs");            File[] arrayOfFile;            if (str != null) {                StringTokenizer localStringTokenizer = new StringTokenizer(str, File.pathSeparator);                int i = localStringTokenizer.countTokens();                arrayOfFile = new File[i];                for (int j = 0; j < i; ++j)                    arrayOfFile[j] = new File(localStringTokenizer.nextToken());            } else {                arrayOfFile = new File[0];            }            return arrayOfFile;        }
        public ExtClassLoader(File[] paramArrayOfFile) throws IOException {            /// super表示URLClassLoader,第一个参数为加载类的路径,第二个参数为Parent为null。            super(getExtURLs(paramArrayOfFile), null, Launcher.factory);            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);        }

AppClassLoader的加载过程与扩展类加载器类似,不同之处在与

///这也是应用类加载器的默认加载路径String str = System.getProperty("java.class.path");
/// 第二个参数即Parent为扩展类加载器 super(paramArrayOfURL, paramClassLoader, Launcher.factory);

在类加载器中,当返回classLoader返回null,表示交给引导类加载器来处理,因此在上面的代码表现出来的关系为:
系统类(应用类)加载器的Parent为扩展类加载器。
扩展类加载器的Parent为(null)引导类(启动类)加载器。

把这段关系理清了,就能看懂下面这段判断是否为扩展类加载器的代码了:

private static boolean isExtClassLoader(ClassLoader paramClassLoader) {        for (ClassLoader localClassLoader = ClassLoader                .getSystemClassLoader(); localClassLoader != null; localClassLoader = localClassLoader.getParent())            if ((localClassLoader.getParent() == null) && (localClassLoader == paramClassLoader))                return true;        return false;    }

弄清三者之间的Parent关系后,就能明白为何这里将localClassLoader != null;作为循环结束条件了。
getSystemClassLoader()返回的是应用类加载器的实例。这也是应用类加载器也称为系统类加载器的原因。

在Launcher代码中,有一段代码是Thread.currentThread().setContextClassLoader(this.loader),即设置线程上下文类加载器,其起作用的范围在子线程和父线程之间,至于为什么会出现这个类加载器但其又不属于我们在其他博客上常常看到的类加载器顺序图中,只能说成也双亲委派,失也双亲委派,双亲委派带来了安全性,从上至下,按路径从核心到外层依次加载相关类库构造VM环境,另外在加载类时,其引用的类也会被同一个类加载器一同加载,并且这些相互有引用关系的类在被同一个类加载器加载后,被标志为同一个命名空间,这也是为什么有时会提到一个类加载器对应一个命名空间(除非显示的声明,否则不同的类加载器加载的类相互之间是不可见的),但对于相互引用关系可以被同一个类加载器加载,却可以出现这样一种情况,即接口与实现接口的类,接口与实现接口的类之间不存在引用的关系,因此实现接口的类并不会被同一个类加载器一同加载,但是当接口使用的加载器并不能在其默认路径上查找到实现接口的类,报出ClassNotFoundException时,你不得不使用上下文类加载器去指定使用某个类加载器去默认的路径上查找这个实现接口的类。

你可以把上下文类加载器看做是一座桥,或者是一个代理,一个中介,它并不是一个类加载器的实现,它只是一个引用,可以表示扩展类加载器,系统类类加载器,自定义类加载器,具体在某个线程中表示的是哪个加载器,需要我们使用Thread.currentThread().setContextClassLoader(this.loader)去指定。

我们使用线程上下文类加载器的目的在于让线程去某个类加载器的默认路径上找到我们要加载的类, 归根结底, 还是路径

对于localClass = findClass(paramString);

protected Class<?> findClass(String paramString) throws ClassNotFoundException {        throw new ClassNotFoundException(paramString);    }

你可以自定义自己的类加载器,是通过继承ClassLoader后重写findClass()中的逻辑。

更新。
双亲委派机制那段代码里,有一行代码是:

if (paramBoolean)                resolveClass(localClass);

resolve这个单词会有迷惑性,并不是解析,解析是由defineClass完成的。
这里写图片描述

思考一个问题,自定义的类加载器如何实现?什么时候需要使用自定义的类加载器?
通过传入字节码进入defineClass方法中,能够返回class文本所描述的类。
这里写图片描述
对于继承自ClassLoader的类,都会被默认视为自定义的类加载器。
所以对于实现一个类加载器:

1.继承自ClassLoader。
2.重写findClass方法。
3.将需要被载入的类的class文本通过defineClass方法以byte数组形式读入。

ClassLoader是一个抽象类。
这里写图片描述
可以看到其内的findClass方法是没有实现的,也即留给我们自定义实现。
这里写图片描述

另外,如果对抽象类和接口的联系以及作用不是很理解,可以看看《JAVA7 程序设计》的抽象类一章。
简单来说,抽象类和接口都是框架设计者出于灵活性或者功能的实现而给使用者描述的一个框架,相比接口,抽象类有更高的灵活性,对于接口,其默认是自带了final和abstract的,虽然外面在定义接口时并没有使用final和abstract两个限制。
抽象类的出现,是为了解决接口不够灵活,对于一个类,并不是其内的每一个方法都是需要用户来实现的,框架设计者可以实现类的一些最基础的功能,余下的方法才有用户自己去实现,或者出于灵活性考虑,用户还可以重写由框架设计者实现的基本方法以实现定制。

什么时候需要使用自定义类加载器?
当为了源码不被反编译导致的源码泄露,需要在生成后对字节码加密,在使用前对字节码解密,加密后的字节码无法被JVM的类加载器识别,需要在解密后再调用自定义类加载器产生类对象。
因此在加载class文件前需要做做一些必要处理的情况出现时,可以使用自定义类加载器。

1 0
原创粉丝点击