classloader魔术

来源:互联网 发布:经济学书籍推荐知乎 编辑:程序博客网 时间:2024/04/28 17:18

最近为了解决类搜索路径的问题,上网查了相关的网页,做了笔记,把一些相关的东西浓缩了一下。
大部分是引用其他人的内容,这里不一一列出来源,希望不要见怪。
从java2开始采用了新的类装载机制
Java虚拟机中的每个Java类都是由某个classloader载入的, classloader本身也是Java类,
有一个例外. Java包含一个自举classloader(Bootstrap ClassLoader),
它是用本地代码写的, 是JVM的一部分.
这个自举classloader的主要作用是载入Java核心类, 从而自举整个Java环境.

JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.
其中,Bootstrap是用本地代码编写的,我们在Java中看不到它,getClassLoader()方法返回null。它用来加载核心类库,在JVM源代码中这样写道:
static const char classpathFormat[] =
"%/lib/rt.jar:"
"%/lib/i18n.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/classes";
知道为什么不需要在classpath中加载这些类了吧?人家在JVM启动的时候就自动加载了。
Extension ClassLoader用来加载扩展类,即/lib/ext中的类。
AppClassLoader负责加载Classpath中定义的类。

三个系统属性决定了Bootstrap ClassLoader、Extension lassLoader和AppClassLoader加载类的搜索路径。
System.getProperties().getProperty("sun.boot.class.path")
System.getProperties().getProperty("java.ext.dirs")
System.getProperties().getProperty("java.class.path")
这三个属性通过启动时的操作系统环境变量和启动option进行设置。
运行时通过System.setProperties改变这些属性,Bootstrap ClassLoader、Extension lassLoader和AppClassLoader的搜索路径不会随着改变。

Bootstrap ClassLoader、Extension lassLoader和AppClassLoader加载类用的是委托模型。
即先让Parent类(不是Super,不是继承关系)寻找,Parent找不到才自己找,
三者的关系为:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。
加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。
自己实现的ClassLoader子类并不要求遵循"委托给父类"的模式,如tomcat5的类装载器系统。

Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。
System Class Loader(AppClassLoader类)是一个特殊的用户自定义类装载器,
由JVM的实现者提供,是创建装载器时默认的父装载器、程序主线程的默认运行环境类装载器。
通过ClassLoader.getSystemClassLoader() 可以得到System Class Loader

在一个对象创建另一个对象时,默认由该对象的类装载器负责装载被创建对象的类
也可以通过指定ClassLoader的loadClass("类的全限定名")得到类的Class对象
得到Class对象后可以通过newInstance()方法或反射构造器方法的到类的实例
得到类装载器的方法有:
1、ClassLoader.getSystemClassLoader() 得到System Class Loader
2、对象通过getClass().getClassLoader()得到自己的装载器;
3、装载器的getParent()方法返回它的父装载器;
4、Thread类的getContextClassLoader() 返回该线程运行环境的类装载器;
5、实现自己的ClassLoader子类;
实现自己的类装载器的主要目的是自己决定类的查找路径,自己实现的类装载器并不要求遵循"委托给父类"的模式。

实现自己的ClassLoader子类后可以通过ClassLoader的loadClass("类的全限定名")得到类的Class对象
得到Class对象后可以通过newInstance()方法或反射构造器方法得到类的实例。
Class类的static Class forName(String name, boolean initialize, ClassLoader loader) 方法也可以用于指定自己的类装载来查找类。

Thread类的setContextClassLoader() 可以设置线程的当前环境类装载器
Thread类的getContextClassLoader() 默认是父线程的Context Class Loader,
应用程序的主线程会把AppClassLoader(System Class Loader)作为其Context Class Loader

一个线程的执行代码用到的类可能由不同类装载器装载
getContextClassLoader() 提供了得到当前执行环境中统一类装载器的手段,例:

在log4j中使用了Thread类的getContextClassLoader()方法得到
当前线程的类装载器,然后用类装载器查找和应用配置。
所以在web application server中不同web应用虽然运行在同一个虚拟机,但是可以使用不同的log4j配置、互不干扰。
即时是同名的categor或appender在不同的web应用中都是独立的。

类装载器标识+java类全限定名才是java虚拟机中类的唯一标识
所以java的单实例实现在java虚拟机中可能并不真的是单实例的

在JDBC实现中,采用了工厂模式,工厂类DriverManager作为java核心类由Bootstrap装载器装载,实现类可能放在classpath中或web application的WEB-INF/lib目录中
那么工厂类默认使用Bootstrap装载器搜索类,是找不到实现类的,除非把实现类放在sun.boot.class.path或java.ext.dirs指定的目录下
实际上是由实现类静态调用工厂类的静态注册方法(一般由Class.forName(className)触发),
DriverManager.getConnection方法中工厂类使用了调用类的类装载器来查找匹配实现类。
所以在web application server中不同web应用虽然运行在同一个虚拟机,但是可以指定自己的jdbc驱动(实现类)版本。

通过自定义类装载器可以实现在一个虚拟机中同时使用一个类的两个不同版本,或者在不同的环境使用不同的版本。
这对j2ee的打包机制实现是非常有用的,如:

当Tomcat5启动的时候,它会首先创建一组class loader,如commonLoader, sharedLoader, catalinaLoader,webappLoader等.其委托模型如下图3所示:
     Bootstrap
          |
     System
          |
     Common
       /      /
Catalina   Shared
                 /
                Webapp1 ...
Tomcat5类装载器委托模型
其中,
1)    Bootstrap 该类装载器装载JAVA_HOME/jre/lib和JAVA_HOME/jre/lib/ext两目录上的JAR包.
2)    System 该类装载器装载当前CLASSPATH上的JAR包.在Windows系统下, CLASSPATH环境变量会在CATALINA_HOME/bin/setclasspath.bat和CATALINA_HOME/bin/catalina.bat文件中被重新设置.
3)    Common 该类装载器装载CATALINA_HOME/common/classes目录中的类, CATALINA_HOME/commons/endorsed和CATALINA_HOME/common/lib目录中的JAR包.
4)    Catalina 该类装载器装载CATALINA_HOME/server/classes和CATALINA_HOME/server/lib目录中的类和JAR包.
5)    Shared 该类装载器装载CATALINA_HOME/shared/classes和CATALINA_HOME/shared/lib目录中的类和JAR包.
6)    WebappX 该类装载器装载WEB-INF/classes和WEB-INF/lib目录中的类和JAR包.

WebappX装载器在请求装载一个类时,它以下面列举的顺序进行:
·         委托Bootstrap ClassLoader: (默认为:JAVA_HOME/jre/lib...)
·         委托ExtClassLoader: (默认为:JAVA_HOME/jre/lib/ext...)
·         委托AppClassLoader (System Class Loader):(当前CLASSPATH上)
·         WebappX本身:/WEB-INF/classes 和/WEB-INF/lib/*.jar
·         委托commonLoader:CATALINA_HOME/common/classes
·         委托commonLoader:CATALINA_HOME/common/endorsed/*.jar
·         委托commonLoader:CATALINA_HOME/common/lib/*.jar
·         委托sharedLoader:CATALINA_HOME/shared/classes 和CATALINA_HOME/shared/lib/*.jar

由于不同的web application server的类装载器系统设计不同的原因,可能导致移植时类搜索路径的问题,运行时出现ClassNotFoundException。

由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。
只有属于同一运行时包的类才能互相访问包可见的类和成员,否则抛出SecurityException。
这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。