Classloader总结

顾名思义, ClassLoader就是类加载器, 而类加载是java程序运行的第一步, 如果没有类加载器来加载类,那么再牛逼的java程序也运行不了, 可见类加载器的重要性。理解类加载器的加载机制, 可以很好的帮助我们理解java类的执行过程, 深入理解java的原理, 帮助我们写出更有效、更高效、更牛逼的程序。

委托机制

java的类加载器采用向上委托机制,需要加载一个类的时候,它的过程如下:

1. 先提交给父加载器去寻找这个类,父加载器再交给它的父加载器, 一直到最顶层的加载器BootstrapClassloader。

2. 如果BootstrapClassloader加载器找到, 那么就直接将加载后的代码交给发起加载过程的加载器去调用, 如果没找到,就交给BootstrapClassloader他的子加载器,也就是ExtClassloader去加载。

3. ExtClassloader如果加载成功, 就把加载后的代码交给发起加载过程的加载器去调用, 如果没找到,就交给ExtClassloader他的子加载器,也就是AppClassloader去加载。

4. AppClassloader重复BootstrapClassloader、ExtClassloader类似的过程, 直到加载类成功, 或者找不到目标类, 抛出ClassNotFoundException。

整个过程如下图所示:

其中BootstrapClassloader是JVM提供的初始化类加载器, 它是所有类加载器的根, 随着jvm启动而启动。

加载目录

如上图中, 我们看到每个加载器加载类的目录都是指定好的, 这个指定好的目录是怎么来的呢? 这里提供一段代码, 大家可以看输出结果和上图中的比较。

@SuppressWarnings("restriction")
    public static void showClassLoaderPath() {
        System.out.println("BootstrapClassLoader: ");
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for(URL url : urls){
            System.out.println(url.getPath());
        }
        System.out.println("BootstrapClassloader的加载目录: " + System.getProperty("sun.boot.class.path"));
        System.out.println("----------------------------");
        URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
        System.out.println(extClassLoader.getClass().getName() + ": ");  
        urls = extClassLoader.getURLs();
        for(URL url : urls) {
            System.out.println(url);  
        }
        System.out.println("ExtClassloader的加载目录: " + System.getProperty("java.ext.dirs"));
        System.out.println("----------------------------");  
        URLClassLoader appClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        System.out.println(appClassLoader.getClass().getName() + ": ");  
        urls = appClassLoader.getURLs();
        for(URL url : urls) {
            System.out.println(url);  
        }
        System.out.println("AppClassloader的加载目录: " + System.getProperty("java.class.path"));
    }

输出结果如下:

BootstrapClassLoader:
/D:/server/java/jdk1.6.0_10/jre/lib/resources.jar
/D:/server/java/jdk1.6.0_10/jre/lib/rt.jar
/D:/server/java/jdk1.6.0_10/jre/lib/sunrsasign.jar
/D:/server/java/jdk1.6.0_10/jre/lib/jsse.jar
/D:/server/java/jdk1.6.0_10/jre/lib/jce.jar
/D:/server/java/jdk1.6.0_10/jre/lib/charsets.jar
/D:/server/java/jdk1.6.0_10/jre/classes
BootstrapClassloader的加载目录: D:\server\java\jdk1.6.0_10\jre\lib\resources.jar;D:\server\java\jdk1.6.0_10\jre\lib\rt.jar;D:\server\java\jdk1.6.0_10\jre\lib\sunrsasign.jar;D:\server\java\jdk1.6.0_10\jre\lib\jsse.jar;D:\server\java\jdk1.6.0_10\jre\lib\jce.jar;D:\server\java\jdk1.6.0_10\jre\lib\charsets.jar;D:\server\java\jdk1.6.0_10\jre\classes
----------------------------
sun.misc.Launcher$ExtClassLoader:
file:/D:/server/java/jdk1.6.0_10/jre/lib/ext/dnsns.jar
file:/D:/server/java/jdk1.6.0_10/jre/lib/ext/localedata.jar
file:/D:/server/java/jdk1.6.0_10/jre/lib/ext/sunjce_provider.jar
file:/D:/server/java/jdk1.6.0_10/jre/lib/ext/sunmscapi.jar
file:/D:/server/java/jdk1.6.0_10/jre/lib/ext/sunpkcs11.jar
ExtClassloader的加载目录: D:\server\java\jdk1.6.0_10\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
----------------------------
sun.misc.Launcher$AppClassLoader:
file:/D:/workspace/98_myproject/jtest/target/classes/
file:/C:/Users/lenovo/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar
file:/C:/Users/lenovo/.m2/repository/ch/qos/logback/logback-classic/1.1.2/logback-classic-1.1.2.jar
file:/C:/Users/lenovo/.m2/repository/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6.jar
file:/C:/Users/lenovo/.m2/repository/ch/qos/logback/logback-core/1.1.2/logback-core-1.1.2.jar
file:/C:/Users/lenovo/.m2/repository/commons-codec/commons-codec/1.9/commons-codec-1.9.jar
file:/C:/Users/lenovo/.m2/repository/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
file:/C:/Users/lenovo/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar
file:/C:/Users/lenovo/.m2/repository/commons-configuration/commons-configuration/1.10/commons-configuration-1.10.jar
file:/C:/Users/lenovo/.m2/repository/org/apache/httpcomponents/fluent-hc/4.3.3/fluent-hc-4.3.3.jar
file:/C:/Users/lenovo/.m2/repository/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar
file:/C:/Users/lenovo/.m2/repository/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar
file:/C:/Users/lenovo/.m2/repository/org/apache/httpcomponents/httpclient-cache/4.3.3/httpclient-cache-4.3.3.jar
file:/C:/Users/lenovo/.m2/repository/org/apache/httpcomponents/httpmime/4.3.3/httpmime-4.3.3.jar
file:/C:/Users/lenovo/.m2/repository/org/quartz-scheduler/quartz/2.2.1/quartz-2.2.1.jar
file:/C:/Users/lenovo/.m2/repository/postgresql/postgresql/9.1-901-1.jdbc4/postgresql-9.1-901-1.jdbc4.jar
file:/C:/Users/lenovo/.m2/repository/commons-dbutils/commons-dbutils/1.5/commons-dbutils-1.5.jar
file:/C:/Users/lenovo/.m2/repository/c3p0/c3p0/0.9.1.2/c3p0-0.9.1.2.jar
file:/C:/Users/lenovo/.m2/repository/junit/junit/4.12-beta-2/junit-4.12-beta-2.jar
file:/C:/Users/lenovo/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar
AppClassloader的加载目录: D:\workspace\98_myproject\jtest\target\classes;C:\Users\lenovo\.m2\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;C:\Users\lenovo\.m2\repository\ch\qos\logback\logback-classic\1.1.2\logback-classic-1.1.2.jar;C:\Users\lenovo\.m2\repository\org\slf4j\slf4j-api\1.7.6\slf4j-api-1.7.6.jar;C:\Users\lenovo\.m2\repository\ch\qos\logback\logback-core\1.1.2\logback-core-1.1.2.jar;C:\Users\lenovo\.m2\repository\commons-codec\commons-codec\1.9\commons-codec-1.9.jar;C:\Users\lenovo\.m2\repository\commons-lang\commons-lang\2.6\commons-lang-2.6.jar;C:\Users\lenovo\.m2\repository\commons-logging\commons-logging\1.1.3\commons-logging-1.1.3.jar;C:\Users\lenovo\.m2\repository\commons-configuration\commons-configuration\1.10\commons-configuration-1.10.jar;C:\Users\lenovo\.m2\repository\org\apache\httpcomponents\fluent-hc\4.3.3\fluent-hc-4.3.3.jar;C:\Users\lenovo\.m2\repository\org\apache\httpcomponents\httpclient\4.3.3\httpclient-4.3.3.jar;C:\Users\lenovo\.m2\repository\org\apache\httpcomponents\httpcore\4.3.2\httpcore-4.3.2.jar;C:\Users\lenovo\.m2\repository\org\apache\httpcomponents\httpclient-cache\4.3.3\httpclient-cache-4.3.3.jar;C:\Users\lenovo\.m2\repository\org\apache\httpcomponents\httpmime\4.3.3\httpmime-4.3.3.jar;C:\Users\lenovo\.m2\repository\org\quartz-scheduler\quartz\2.2.1\quartz-2.2.1.jar;C:\Users\lenovo\.m2\repository\postgresql\postgresql\9.1-901-1.jdbc4\postgresql-9.1-901-1.jdbc4.jar;C:\Users\lenovo\.m2\repository\commons-dbutils\commons-dbutils\1.5\commons-dbutils-1.5.jar;C:\Users\lenovo\.m2\repository\c3p0\c3p0\0.9.1.2\c3p0-0.9.1.2.jar;C:\Users\lenovo\.m2\repository\junit\junit\4.12-beta-2\junit-4.12-beta-2.jar;C:\Users\lenovo\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar

可以看到和图中所示的各个类加载器加载范围是一致的, 同时我们也可以看到, jvm提供的加载器所加载的目录所对应的系统属性值。

依赖顺序

那么问题来了。 假如我们的项目有个依赖包A被放到了jre/lib/ext目录下, 而这个依赖包依赖的另一个依赖包B放在项目目录, 这个时候我们可以加在成功吗?

答案是不可以的。

因为java的类加载机制是向上委托, 而不是向下委托, 也就是说ExtClassloader可以调用BootstrapClassLoader加载的类, AppClassLoader可以调用ExtClassloader和BootstrapClassLoader加载的类, 而这个过程反过来是不行的。

在我们的这个问题中, 依赖包A被ExtClassloader加载, B被AppClassloader加载, 这个时候A要引用B包中的类, 按照我们上面讲的机制是不可以的。 当然它并不是绝对的, java提供了一种绕开上述机制的方法, 下面我们会讲到。

反向依赖

那么怎么可以突破向上委托这种机制呢?

JDK 1.2提供了一个叫线程上下文类加载器, 对应代码就是java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。

那么什么情况下以上类加载机制会失效呢?

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。 常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。 引用自: http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

自定义类加载器

大多数情况下jdk提供的类加载器已经足够我们使用, 但是在某些特殊情况下, 需要我们编写自定义的类加载器来实现特定功能, 如OSGI框架。如我们在网络上传输类文件的时候对方加密了, 我们就要编写对应解密的类加载器来加载对方发过来的类。

自定义类加载器主要需要注意的就是继承ClassLoader, 并实现其中的findClass方法, 如下:

public class MyClassLoader extends ClassLoader{
    @Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {}
}

这里再引用网络上其他人写好的一个类加载器示例来帮助大家更好的掌握自定义类加载器。

public class FileSystemClassLoader extends ClassLoader { 

    private String rootDir; 

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    } 

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length);
        }
    } 

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    } 

    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace(‘.‘, File.separatorChar) + ".class";
    }
 }
时间: 2024-10-27 07:22:34

Classloader总结的相关文章

java 使用ClassLoader动态加载jar,zip文件

java ClassLoader的使用 public class Main {     public static void main(String[] args) {                  URL[] urls = new URL[] {};         MyClassLoader classLoader = new MyClassLoader(urls, null);         String path = "E:/WorkSpace/MapSDKLibrary/libs

myBatis中的注解@Param、返回值为Map、JAVA读取Excel并解析文本、Class.getResource()和ClassLoader.getResource()

myBatis中的注解@Param:http://blog.csdn.net/gao36951/article/details/44258217:  http://www.cnblogs.com/thomas12112406/p/6217211.html. myBatis返回值为Map:http://blog.csdn.net/werewr342352321df/article/details/11892755. ====================== JAVA读取Excel并解析文本:h

ClassLoader.getSystemResourceAsStream()

一: 要加载的文件和.class文件在同一目录下,例如:com.x.y 下有类Test.class ,同时有资源文件config.properties 那么,应该有如下代码: //前面没有"/"代表当前类的目录 InputStream is1 = Test.class.getResourceAsStream("config.properties"); System.out.println(is1);// 不为null   第二:在Test.class目录的子目录下,

classloader

4 Jboss 启动及加载过程 详细参考: http://tech.it168.com/j/2007-06-27/200706271521984.shtml 1) org.jboss.Main.main(String[]) 为入口 . 2) main 函数创建一个名叫” jboss ”的线程组 , 然后创建一个属于该组的线程 , 在线程中执行 boot 方法 . 3) boot 方法首先处理 main 函数中的参数 ( 及一些其它的系统环境设置 ), 接着就用系统的属性创建了org.jboss.

深入浅出ClassLoader

你真的了解ClassLoader吗? 这篇文章翻译自zeroturnaround.com的 Do You Really Get Classloaders? ,融入和补充了笔者的一些实践.经验和样例.本文的例子比原文更加具有实际意义,文字内容也更充沛一些,非常感谢作者 Jevgeni Kabanov 能够共享如此优秀的文档. 1. 为什么你需要了解和敬畏ClassLoader ClassLoader在Java语言中占据了核心地位,Java应用服务器,OSGi,以及大量的网络框架,它们大多数都用到了

[Java开发之路](18)关于Class.getResource和ClassLoader.getResource的路径问题

Java中取资源时,经常用到Class.getResource和ClassLoader.getResource.昨天老师讲解题目时候,问我们为什么你们都是在文件前家上"/": String path = Resources.class.getResource("/a.txt").getPath(); 注:在Resources文件下创建了a.txt文件 我想我反正是试出来的,不使用"/"不行.为了正式解答心中的疑惑,我们正式来看看Resources

Java ClassLoader 原理详细分析

一.什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常.而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的

Java ClassLoader深入讲解(转)

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader                |       extension classloader                |       system classloader bootstrap classloader -引导(也称为原始)类加载器,它负责加载Java的核心类.在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 - D选

ClassLoader.getResourceAsStream(name); 获取配置文件的方法

ClassLoader.getResourceAsStream(name);路径问题 InputStream in = getClass().getResourceAsStream('/'+"spring-beans.dtd"); 表示从classs目录下面的找文件,文件放在src下面就可以了.InputStream in = getClass().getResourceAsStream("spring-beans.dtd"); 表示从当前classs下面的路径找文

java类加载器——ClassLoader

Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载. 如果你平时只是做web开发,那应该很少会跟类加载器打交道,但如果你想深入学习tomcat服务器的架构,它是必不可少的.所谓类加载器,就是用于加载Java类到Java虚拟机中,它负责读取Java字节码,并转换成java.lang.Class类的一个实例,使字节代码.class文件得以运行.一般类加载器负责根据一个指定的类找到对应的字节代码,然