深刻理解java的配置环境以及java的执行过程对做好开发是十分重要的。
类加载器ClassLoader 便是其中非常重要的概念。
本文简单并演示Java类加载器的一些特点,不妥之处,敬请指出。
背景:
A.java 引用 了B.jar 中的一个文件,B.jar 包中的C.class 使用了C.class.getResourceAsStream("/jdbc.xml")获取资源文件。 B.jar 做一个独立的功能的工具,使用方只要提供对应的jdbc.xml 配置文件即可在dev qa prod 各个环境使用啦。
但是问题来了,在web下和java命令行情况下,jdbc.xml 文件放置的位置大不相同,因为web 和java命令下 B.jar 中加载C.class的类加载器不同!
类加载器的分类:
1、启动类加载器(Bootstrap ClassLoader) 这个类有c++语言实现,是虚拟机的一部分。
2、所有其他类加载器。其他类加载器都继承抽象类java.lang.ClassLoader。
做为java的大部分项目,都是有以下三类类加载器加载的:
1、启动类加载器。加载的内容为:<JAVA_HOME/lib 目录中的jar包。
2、扩展类加载器。加载的内容为:java.ext.dirs 系统变量指定的路径中所有类库。实现为:sun.misc.Launcher$ExtClassLoader 实现。
3、应用类加载器。实现为:sun.misc.Launcher$AppClassLoader。负责加载用户类路径上指定的类库。
下面我们看个例子:
package org.job.user;
import org.apache.commons.lang.StringUtils;
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(StringUtils.class.getClassLoader());
System.out.println(StringUtils.class.getResource("/"));
System.out.println(Test.class.getResource("/"));
System.out.println(StringUtils.class.getResourceAsStream("/jdbc.xml"));
System.out.println(Test.class.getResourceAsStream("/jdbc.xml"));
}
}
此时如果大家到Test.java 所在的目录,执行javac Test.java 会报错:
为什会报错,因为org.apache.commons.lang.StringUtils 找不到,当前AppClassLoader 找不到StringUtils 类的路径。
找到StringUtils 可以通过指定-classpath .;D:\DevPlatform\m2localRepository\commons-lang\commons-lang\2.5\commons-lang-2.5.jar 来找到。
大家注意到输出:
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$AppClassLoader@19821f
file:/E:/spring/spring-aop-aspectj/target/test-classes/
file:/E:/spring/spring-aop-aspectj/target/test-classes/
java.io.BufferedInputStream@1fb8ee3
java.io.BufferedInputStream@61de33
Test.class 和StringUtils.class 都是通过同一个类加载器(AppClassLoader)加载的。另外 通过Test.class.getResourceAsStream("/jdbc.xml") 找到了jdbc.xml 文件,jdbc.xml 文件在E:\spring\spring-aop-aspectj\src\test\java 也即是我执行java 和 javac 的位置。
这说明:AppClassLoader 的根目录在执行环境的目录。
下一步我们演示通过ExtClassLoader 加载,即通过-Djava.ext.dirs=D:\DevPlatform\m2localRepository\commons-lang\commons-lang\2.5 来加载commons-lang-2.5.jar包。
从上面的输出我们看到Test.class 和 StringUtils.class 的类加载器不同了,另外 System.out.println(StringUtils.class.getResourceAsStream("/jdbc.xml")); //没有找到对应的资源文件
System.out.println(Test.class.getResourceAsStream("/jdbc.xml")) //找到了对应的资源文件
为什么StringUtils.class 找不到对应的资源文件了,因为getResourceAsStream 是通过委托对应的类加载器来加载的,"/jdbc.xml" 通过不同的类加载器来加载,对于的资源文件的位置是不同的。
我在commons-lang-2.5.jar 包所在目录创建com 并在其下建立jdbc.xml 文件,继续运行结果如下:
看到上面的输出,一切都不言而喻。
总结:
对于Class.getResourceAsStream("/*.*") 是通过委托给对应的ClassLoader来加载的,其对应的资源文件的根目录为:
ExtClassLoader: 为java.ext.dirs 指定的目录下的所以目录。优先使用相应jar包的路径对应的根目录。比如java.ext.dirs=/home/q/,那么其根目录为/home/q/*dir。
AppClassLoader: 执行java javac 命令的目录。
注意:AppClassLoader寻找跟资源文件优先使用java.ext.dirs 指定的资源文件,如果没有才会到 命令执行的目录下寻找。 另外java.ext.dirs 指定的资源文件 最好不要有多个重名的文件,否则将不确定使用的是哪一个。
验证留给大家吧