类加载器详细介绍

在这里插入图片描述

类与类加载器的关系

类加载器用于实现类的加载动作,但它在java中起到的作用远远不仅于类加载阶段。对于任何一个类来说,都需要类本身和加载这个类的类加载器一同确立其在java JVM中的唯一性。通俗来讲:比较两个类是否相等,只有两个类是由同一个类加载器加载的前提下才有意义。否则,即使两个类来自同一个class文件,但是由于加载他们的类加载器不同,那这两个类就必不相等。
这里的“相等”,包括代表类的Class对象的equals()方法,isAssignableForm()方法和,isInstance()方法的返回结果。也包括instanceOf关键字对象所属关系判定等情况。如果没有注意到类加载器的影响,在某些情况下会产生迷惑人的结果。

在Java中,类的加载过程是在程序运行时动态进行的。Java的类加载模型可以分为三个步骤:加载、连接和初始化。

类加载过程:加载

首先是加载阶段,也就是将类的字节码加载到内存中。在Java中,有三种不同的类加载器:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。

Bootstrap ClassLoader是最顶层的类加载器,负责加载JRE的核心类库,如java.lang包中的类。
Extension ClassLoader负责加载Java的扩展类库。
Application ClassLoader则负责加载应用程序的类。

类加载过程:连接

接下来是连接阶段,也就是将加载的字节码转换为可执行的代码。连接阶段分为三个步骤:验证、准备和解析。

在验证阶段,Java会对字节码进行验证,确保其符合Java虚拟机规范。
在准备阶段,Java会为类的静态变量分配内存,并将其初始化为默认值。
在解析阶段,Java会将符号引用解析为直接引用。

类加载过程:初始化

最后是初始化阶段,也就是执行类的构造函数,并执行静态变量的赋值操作。在Java中,类的初始化是线程安全的,因为只有一个线程会执行初始化操作。

什么是双亲委派模型

那么,什么是双亲委派模型呢?简单来说,就是在类加载时,先由父类加载器去加载,如果父类加载器找不到该类,才由子类加载器去加载。这种模型的好处是可以保证Java程序的安全性和稳定性,因为如果父类加载器已经加载了一个类,子类加载器就不需要再加载一遍,避免了出现类似于同名类被重复加载的情况。但是,双亲委派模型也有一些缺点,例如无法实现对于同一个类的不同版本的加载,因为父类加载器会优先加载已经存在的类,导致子类加载器无法加载另一个版本的同名类。

为什么Tomcat要自定义类加载器

那么,为什么Tomcat要自定义类加载器呢?这是因为在Tomcat中,有多个Web应用程序需要加载自己的类库。如果使用Java默认的双亲委派模型,可能会导致同名类库被多个Web应用程序重复加载,浪费内存资源。所以,Tomcat使用自定义的类加载器来实现Web应用程序之间的隔离,确保每个Web应用程序只加载自己的类库。

自定义类加载器的场景:

除了Tomcat这种容器框架需要自定义类加载器之外,还有其他一些场景也可能需要自定义类加载器。下面列举一些常见的场景:

(1)插件化开发:是一种常见的开发模式,通过动态加载插件,可以使应用程序具有更强的可扩展性。在插件化开发中,通常会涉及到加载不同的插件,这就需要使用自定义类加载器来实现不同插件的隔离。自定义类加载器可以使插件之间相互独立,不会相互影响。

(2)热部署:在一些特殊场景下,可能需要对应用程序进行热部署,即在应用程序运行过程中替换某些类。为了实现热部署,需要使用自定义类加载器,可以在加载类时重新读取字节码文件,从而实现对类的更新。这种方式可以避免应用程序的重启,提高了应用程序的可用性。

(3)多版本控制:在一些应用程序中,可能需要同时使用多个版本的同名类。例如,在进行系统升级时,可能需要同时存在新旧两个版本的类,以保证系统的兼容性。为了实现多版本控制,需要使用自定义类加载器,可以将不同版本的类隔离开来,避免冲突。

(4)实现类似于Java EE容器的类加载器层次结构:在一个Web应用程序中,可以定义多个类加载器,每个类加载器负责加载特定类型的类,并且它们之间形成了一种父子关系。这样可以实现对不同类的隔离和管理。

(5)防止Java反序列化漏洞:Java序列化和反序列化可以用于将Java对象转换为字节流以及从字节流中还原Java对象。但是,Java序列化机制存在一些安全漏洞,攻击者可以通过反序列化来执行恶意代码。为了避免这种情况,可以使用自定义类加载器来限制反序列化操作只在特定的安全上下文中进行。

(6)加载非标准格式的类文件:有时候,我们可能需要加载非标准格式的类文件,如动态生成的类或嵌入式Java类等。这些类文件可能无法被标准的类加载器加载,这时候就需要自定义类加载器来加载这些类文件。

示例:
假设我们有一个电商项目,其中包含了一个名为“Order”的类,我们需要在项目中同时使用两个版本的“Order”类,一个是1.0版本,一个是2.0版本。这时候,我们就可以自定义一个类加载器,通过指定不同的类加载路径,分别加载两个版本的“Order”类。
具体实现代码如下:
在这里插入图片描述
在这个示例中,我们自定义了一个名为CustomClassLoader的类加载器,它接受一个classPath参数,表示要加载类的路径。在findClass方法中,我们首先通过loadClassData方法读取字节码文件,然后通过defineClass方法将字节码文件转换为Class对象返回。

为了演示如何加载两个版本的同名类,我们可以分别将两个版本的Order类放置于不同的路径中,然后分别使用两个CustomClassLoader实例加载它们。具体示例如下:
在这里插入图片描述

不同类加载器加载的类如何相互访问:

如果两个类由不同的类加载器加载,那么这两个类就不属于同一个命名空间,也就是说,它们在 Java 虚拟机中是两个独立的类。但是,这两个类仍然可以相互访问,只需要使用反射或者其他方式来获取到另一个类的 Class 对象,然后就可以进行访问了。

例如,如果有一个类 A 和另一个类 B,它们由不同的类加载器加载,那么可以使用下面的代码来访问 B 类的静态属性或方法:
Class<?> clazz = Class.forName("B", true, classLoader);
Field field = clazz.getDeclaredField("staticField");
field.setAccessible(true);
Object value = field.get(null);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值