java双亲委派模式
双亲委派模式是Java类加载器在加载类时使用的一种机制。在这种模式下,如果一个类加载器收到了类加载请求,它并不会自己首先去加载,而是把这个请求委派给父类加载器去执行,每一层都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这种模式的设计初衷是为了保证Java程序运行的稳定性和安全性
在Java的世界里,类加载器(ClassLoader)扮演着至关重要的角色。它负责将字节码转换为JVM可以执行的Java类。其中,一个重要的概念就是双亲委派模式(Parent Delegation Model)。本文将详细介绍双亲委派模式,包括其定义、工作原理、优点,以及通过Java代码示例进行详细解析。
一、什么是双亲委派模式?
双亲委派模式是Java类加载器在加载类时使用的一种机制。在这种模式下,如果一个类加载器收到了类加载请求,它并不会自己首先去加载,而是把这个请求委派给父类加载器去执行,每一层都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
这种模式的设计初衷是为了保证Java程序运行的稳定性和安全性。由于类加载器的工作是将类的字节码文件转换为JVM可以执行的类,因此如果不同的类加载器加载了同一个类,就可能会出现版本不一致的问题,导致程序运行出错。通过双亲委派模式,可以确保一个类只会被加载一次,从而避免了这种问题。
二、双亲委派模式的工作原理
双亲委派模式的工作原理可以概括为以下几步:
- 启动类加载器首先尝试加载类。如果找到了类的字节码文件,就结束加载过程,否则进入下一步。
- 父类加载器尝试加载类。如果找到了类的字节码文件,就结束加载过程,否则进入下一步。
- 子类加载器尝试加载类。如果找到了类的字节码文件,就结束加载过程,否则抛出ClassNotFoundException。
这个过程可以用以下的Java代码示例来描述:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先, 检查是否已经加载过这个类
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (getParent() != null) {
// 如果父类加载器不为null, 则让父类加载器尝试加载这个类
c = getParent().loadClass(name, false);
} else {
// 如果父类加载器为null, 则由启动类加载器尝试加载这个类
c = getSystemClassLoader().loadClass(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都无法加载这个类, 则由自己加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
在这个示例中,我们可以看到,当一个类加载器收到加载类的请求时,它首先会检查这个类是否已经被加载过。如果这个类已经被加载过,就直接返回这个类;否则,它会尝试让父类加载器去加载这个类。如果父类加载器也无法加载这个类,它才会尝试自己去加载。
这种机制的好处是,它可以确保一个类只会被加载一次,从而避免了版本不一致的问题。同时,由于父类加载器总是先于子类加载器进行类加载,因此可以确保核心Java类库总是由最高优先级的类加载器(即启动类加载器)加载,从而保证了Java程序的稳定性和安全性。
三、双亲委派模式的优点
双亲委派模式的设计思想是为了保证Java核心库的类型安全,所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么很可能会在JVM中存在多个版本的java.lang.Object类,而这些类之间是不兼容的,即便是同名的类,被不同的类加载器加载后,也是完全不同的两个类,这就破坏了双亲委派模式的规则。
此外,双亲委派模式还有以下几个优点:
-
避免类的重复加载:Java虚拟机对于每一个加载过的类,都会保存一个对应的元数据。如果不同的类加载器都可以加载同一个类,那么就会产生多个相同的元数据,这不仅会浪费内存空间,还可能会导致程序出错。
-
保护Java核心库的安全:通过双亲委派模式,可以确保核心Java类库总是由最高优先级的类加载器(即启动类加载器)加载,这样可以防止恶意代码篡改核心类库,保证了Java程序的安全。
-
保持类加载器的独立性:每个类加载器都有自己的命名空间,可以加载自己命名空间内的类,但不能加载其他命名空间的类。这样,即使不同的类加载器加载了同名的类,也不会产生冲突。
四、Java代码示例
下面我们通过一个Java代码示例来进一步理解双亲委派模式。首先,我们定义一个自定义的类加载器:
public class CustomClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("Loading Class '" + name + "'");
if (name.startsWith("com.myapp")) {
System.out.println("Loading Class using CustomClassLoader");
return getClass(name);
}
return super.loadClass(name);
}
private Class<?> getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', '/') + ".class";
byte[] b = null;
try {
// This loads the byte code data from the file
b = loadClassFileData(file);
// defineClass is inherited from the ClassLoader class
// and converts the byte array into a Class
Class<?> c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private byte[] loadClassFileData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
然后,我们可以使用这个自定义的类加载器来加载我们的应用类:
public class MyClass {
static {
System.out.println("MyClass has been loaded");
}
public MyClass() {
System.out.println("MyClass has been instantiated");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader myClassLoader = new CustomClassLoader();
Class<?> myClass = myClassLoader.loadClass("com.myapp.MyClass");
MyClass object = (MyClass) myClass.newInstance();
}
}
在这个示例中,我们可以看到双亲委派模式的工作过程:当我们尝试加载MyClass类时,CustomClassLoader首先会检查这个类是否已经被加载过,如果没有被加载过,它会尝试让其父类加载器(系统类加载器)去加载这个类;只有当父类加载器无法加载这个类时,CustomClassLoader才会尝试自己去加载。
五、双亲委派模式的实际应用
在实际的Java开发中,双亲委派模式被广泛应用。例如,Java的Web服务器在加载Web应用时,通常会为每个Web应用创建一个独立的类加载器。当一个Web应用需要加载一个类时,它的类加载器会首先检查这个类是否已经被加载过,如果没有被加载过,它会尝试让父类加载器(通常是系统类加载器)去加载这个类;只有当父类加载器无法加载这个类时,它才会尝试自己去加载。
这样做的好处是,可以避免不同的Web应用之间因为类加载的问题而产生冲突。例如,如果两个Web应用都使用了同一个第三方库,但是版本不同,那么如果不使用双亲委派模式,就可能会因为类加载的问题而导致程序运行出错。
此外,双亲委派模式也被用于Java的热部署技术。热部署技术允许我们在不停止应用的情况下更新应用的代码。这是通过创建一个新的类加载器,使用这个类加载器加载新的类实现的。由于新的类加载器和原来的类加载器是不同的,因此它们加载的类也是不同的,即使类的名称相同。这样,我们就可以在不影响正在运行的应用的情况下,加载新的类。
六、双亲委派模式的深入解析
1. 双亲委派模式与其他模式的对比
双亲委派模式是类加载器的一种设计模式,但并非所有的类加载器都遵循这种模式。例如,OSGi、Jboss、Weblogic等框架的类加载器就不遵循双亲委派模式,而是采用了所谓的“子优先”模式。
在“子优先”模式中,类加载器在接收到类加载请求时,会首先尝试自己去加载这个类,只有在自己无法加载的情况下,才会委托给父类加载器去加载。这种模式的优点是,它可以让每个模块都有自己独立的类加载器,从而实现模块的热部署和卸载。
然而,“子优先”模式也有一些缺点。由于每个模块都有自己的类加载器,因此可能会出现同一个类被多次加载的情况,这不仅会浪费内存,还可能会导致版本不一致的问题。因此,“子优先”模式通常只用在需要支持模块热部署和卸载的场景,而在其他场景下,我们通常还是会选择使用双亲委派模式。
2. 双亲委派模式的实现细节
在Java的类加载器中,双亲委派模式是通过以下几个方法实现的:
-
loadClass(String name, boolean resolve)
: 这是类加载器的主要方法,它负责处理类加载请求。这个方法首先会检查这个类是否已经被加载过,如果已经被加载过,就直接返回这个类;否则,它会尝试让父类加载器去加载这个类。如果父类加载器无法加载这个类,它才会尝试自己去加载。 -
findLoadedClass(String name)
: 这个方法用于检查一个类是否已经被加载过。它会在类加载器的内部缓存中查找这个类,如果找到了,就返回这个类;否则,返回null。 -
findClass(String name)
: 这个方法用于加载一个类。它需要被子类重写,以实现具体的类加载逻辑。 -
defineClass(byte[] b, int off, int len)
: 这个方法用于将字节码转换为Class对象。它是一个受保护的方法,只能被类加载器内部调用。 -
resolveClass(Class<?> c)
: 这个方法用于解析一个类。它会确保这个类的所有依赖都已经被加载。
通过这些方法,我们可以实现自己的类加载器,并且可以根据需要选择是否遵循双亲委派模式。
3. 双亲委派模式的应用场景
双亲委派模式主要应用于Java的类加载机制,但其设计思想也可以应用于其他领域。例如,在软件架构设计中,我们可以使用双亲委派模式来组织各个模块的依赖关系。在这种模式下,每个模块都有一个“父模块”,当一个模块需要使用某个功能时,它会首先请求其“父模块”提供这个功能,只有当“父模块”无法提供时,这个模块才会尝试自己实现这个功能。这种模式可以避免功能的重复实现,同时也可以保证模块的独立性。
此外,双亲委派模式也可以用于设计权限管理系统。在这种系统中,每个用户都有一个“父用户”,当一个用户需要执行某个操作时,系统会首先检查其“父用户”是否有这个权限,只有当“父用户”没有这个权限时,系统才会检查这个用户是否有这个权限。这种模式可以有效地管理用户的权限,避免权限的滥用。
七、总结
双亲委派模式是Java类加载器的一个重要特性,它可以确保Java核心库的类型安全,并且可以防止Java类的多次加载。通过理解和掌握双亲委派模式,我们可以更好地理解Java的类加载机制,并且可以在需要的时候,编写自己的类加载器。同时,双亲委派模式也是Java的许多高级特性,如Web应用的隔离、热部署等技术的基础。
👉 💐🌸 公众号请关注 "果酱桑", 一起学习,一起进步! 🌸💐
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)