类加载器:
负责将.class文件加载到内存中,并为之生成对应的Class对象,也就是字节码文件对象。
问题:我们平时书写在eclipse中的Java程序是如何运行的呢?
1)首先将 .java 源文件编译为class类文件;
2)编译后的类文件是存在硬盘中的,那么我们运行需要在内存中看到效果,那么类文件是如何被加载到内存中的呢,就是jvm通过类加载器ClassLoader把硬盘中的class文件加载到内存中,这样就可以使用这个类中的成员变量和方法了。而被加载到内存中这个class文件就会变成一个Class类的对象。
常见的类加载器有三种,每个类加载器负责加载不同位置的类:
1)Bootstrap 根类加载器;
2)ExtClassLoader 扩展类加载器;
3)AppClassLoader 系统/应用类加载器;
那么这三种类加载器各有什么作用或者有什么区别呢?
他们三个加载的范围是不一样的。如下图所示:
说明:
1)Bootstrap是最顶级的类加载器。它加载类文件不是我们自己书写的,负责Java核心类的,比如System,String等。只有所有类加载到内存中,我们才可以使用。
2)ExtClassLoader 扩展类加载器,加载的是扩展类的,我们是用不到的,都是jdk内部自己使用的。
3)AppClassLoader 系统/应用类加载器,是用来加载ClassPath 指定的所有jar或目录,ClassPath表示存放类路径的,我们如果不配置ClassPath,那么就表示当前文件夹,在idea环境下的ClassPath是out目录。在out目录存放的都是我们书写好的class文件,也就是说 AppClassLoader 类加载器是用来加载我们书写的out目录下的class文件。
需求:演示类加载器的父子关系。
代码演示如下所示:
分析:如何获取一个类的类加载器呢?
如果想获得当前类的加载器,那么首先必须获得当前类的字节码文件对象,而这个字节码文件对象属于Class类型,我们可以使用 Class类中的getClassLoader()函数来获得类加载器:
ClassLoadergetClassLoader() 返回该类的类加载器
AppClassLoader:加载classPath中的所有的类,也就是我们自己写的那些类!
注意:类加载器,也是一个类,也需要被加载。一般类加载器都是被父类加载器加载的!
获取父类加载器的方法:使用ClassLoader 类中的getParent()返回委托的父类加载器 。
说明:AppClassLoader是被ExtClassLoader加载的!
ExtClassLoader肯定也是一个类,需要被父加载,它的父亲是BootStrap。
那么问题来了:如果这个类加载器也需要被人加载,那么就没有尽头了!因此,BootStrap是不需要被加载的。
因为它不是一个Java类。它是用C++实现的一段代码。
也就是说,jvm虚拟机一启动就会运行C++实现的这段代码,那么BootStrap类一旦被启动就会开始加载他下面的子类了。
注意:最顶级的类加载器不是Java类,而是C++实现的代码。
/*
* 演示类加载器的父子关系:
* 获取一个类加载器:使用Class类中的函数:ClassLoadergetClassLoader() 返回该类的类加载器
* 获取Class类的对象 :类名.class
* AppClassLoader:加载classPath中的所有的类,也就是我们自己写的那些类!
* 类加载器,也是一个类,也需要被加载。一般类加载器都是被父类加载器加载的!
* 获取父类加载器的方法:使用ClassLoader类中的函数:
* ClassLoadergetParent()返回委托的父类加载器
*
* AppClassLoader类加载器是被ExtClassLoader类加载器加载的!
*
* ExtClassLoader肯定也是一个类,需要被父类加载器加载,它的父类是BootStrap类加载器。
* 而ExtClassLoader确实被他的父类BootStrap类加载器加载的,
* 那么问题来了:如果BootStrap类加载器也需要被人加载,那么就没有尽头了!因此,BootStrap类加载器是不需要被加载的。
* 因为它不是一个Java类。它是用C++语言实现的一段代码。
* 所以这里获取不到BootStrap类加载器,就是因为他是一段C++代码实现的。
*/
public class ClassLoaderDemo1 {
public static void main(String[] args) {
// 获取当前类的加载器
ClassLoader loader = ClassLoaderDemo1.class.getClassLoader();
//输出当前类的类加载器
System.out.println(loader);//sun.misc.Launcher$AppClassLoader@b0014f0
//获取AppClassLoader类加载器的父类
ClassLoader parent = loader.getParent();
//输出AppClassLoader类加载器的父类加载器
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@325e9e34
//获取ExtClassLoader类加载器的父类
ClassLoader grandpa = parent.getParent();
//输出ExtClassLoader类加载器的父类加载器
System.out.println(grandpa);//null
}
}
注意:
通过上述学习我们发现三种类加载器都有自己要加载的类文件,各司其职,不能乱加载,比如Bootstrap类加载器只能加载sun公司定义好的类,他就不能加载自定义的类文件。所有的自定义类文件都是由AppClassLoader类加载器加载。
需求:用类加载器加载配置文件。
代码步骤:
1)新建一个类ClassLoaderDemo2 ,并书写main函数;
2)在当前项目下新建一个stu.ini文件,并在其中输入:
name=zhangsan
age=19
3)创建Properties 类的集合对象p;
4)创建字节输入流FileInputStream对象关联项目根目录文件stu.ini;
5)使用集合对象p调用load函数加载配置文件中的内容,并使用输出语句输出内容即可;
使用之前的FileInputStream方式加载配置文件方式加载文件内容:
//使用之前的加载配置文件方式加载文件内容
public void method_1() throws Exception{
// 创建Properties类的对象
Properties p = new Properties();
//调用函数加载配置文件中内容
p.load(new FileInputStream("stu.ini"));
//输出内容
System.out.println(p);
}
说明:直接使用FileInputStream的相对路径,是相对于项目。
注意:我们在开发中配置文件stu.ini不仅可以存放到项目的根目录,有时我们还会存放到src下面:
这样我们使用new FileInputStream("stu.ini")在读取就会出现如下问题:
错误原因:new FileInputStream("stu.ini")是去项目的根目录去查找stu.ini文件,而由于我们已经将该文件移动到src下面,所以会报找不到指定文件的异常。
既然使用之前的加载方式加载src下面的配置文件有问题,那么我们可以使用新的加载方式,就是用类加载器加载。
分析步骤:
1)使用当前类ClassLoaderDemo2获得Class对象并调用Class类中的getClassLoader()函数:
ClassLoaderloader = ClassLoaderDemo2. class .getClassLoader();
2)使用类加载器对象loader 调用ClassLoader 类中的InputStream getResourceAsStream(String name)返回读取指定资源的输入流
InputStream in =loader.getResourceAsStream("stu.ini");
说明:这里的name是文件的路径:这个路径如果使用相对路径,相对的是src目录。
代码演示如下所示:
//使用之前的加载配置文件方式加载文件内容
public void method_1() throws Exception{
// 创建Properties类的对象
Properties p = new Properties();
//获取当前类的加载器
ClassLoader loader = ClassLoaderDemo2.class.getClassLoader();
//使用类加载器对象调用函数获取加载配置文件的字节输入流
InputStream in = loader.getResourceAsStream("stu.ini");
//调用函数加载配置文件中内容
// p.load(new FileInputStream("stu.ini"));
p.load(in);
//输出内容
System.out.println(p);
}
总结:这两种加载文件的区别在于加载的相对位置不一样,第一种方式相对的是当前项目,而第二种方式相对的是src。