什么是双亲委派机制
双亲委派机制(Parent Delegation Mechanism)是Java中的一种类加载机制。在Java中,类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。
这种机制的设计目的是为了保证类的加载是有序的,避免重复加载同一个类。Java中的类加载器形成了一个层次结构,根加载器(Bootstrap ClassLoader)位于最顶层,它负责加载Java核心类库。其他加载器如扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)都有各自的加载范围和职责。通过双亲委派机制,可以确保类在被加载时,先从上层的加载器开始查找,逐级向下,直到找到所需的类或者无法找到为止。
这种机制的好处是可以避免类的重复加载,提高了类加载的效率和安全性。同时,它也为Java提供了一种扩展机制,允许开发人员自定义类加载器,实现特定的加载策略。

双亲委派机制优缺点
优点:
- 避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。
- 安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。
- 扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。
缺点:
- 灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。
- 破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。
- 不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外的处理。
双亲委派机制应用场景
双亲委派机制在Java中有多种应用场景。一些常见的应用场景包括:
- 类加载:双亲委派机制主要用于Java中的类加载。它确保类以层次结构方式加载,从引导类加载器开始,然后是扩展类加载器,最后是应用程序类加载器。这样可以高效且一致地加载类,跨类加载器层次结构。
- 安全性:双亲委派机制在确保Java应用程序的安全性方面起着重要作用。通过委派类加载给父加载器,它可以防止未经授权或恶意代码的加载和执行。这有助于维护Java运行时环境的完整性和安全性。
- 类库:双亲委派机制对于加载和管理类库非常有用。引导类加载器位于层次结构的顶部,负责加载核心Java类。扩展类加载器负责从Java扩展目录加载类。应用程序类加载器负责从应用程序的类路径加载类。这种分离允许高效组织和管理类库。
- 自定义类加载:开发人员可以利用双亲委派机制实现具有特定加载行为的自定义类加载器。通过扩展现有的类加载器并重写类加载过程,开发人员可以引入自定义逻辑,从非标准位置加载类或对加载的类应用自定义转换。
双亲委派机制在Java中广泛应用于类加载、安全性强制执行和类库管理等方面,为Java应用程序提供了结构化和安全的环境。
双亲委派机制原理
双亲委派机制是Java中的一种类加载机制。其原理如下:
- 当Java程序需要加载一个类时,首先会委托给当前类加载器的父类加载器进行加载。
- 父类加载器会按照相同的方式尝试加载该类。如果父类加载器能够成功加载该类,则加载过程结束。
- 如果父类加载器无法加载该类,则会将加载请求再次委托给它的父类加载器,直到达到顶层的引导类加载器。
- 引导类加载器是Java虚拟机内置的类加载器,它负责加载核心类库,如java.lang包下的类。
- 如果引导类加载器也无法加载该类,则会回到初始的类加载器,尝试使用自身的加载机制加载该类。
- 如果自身的加载机制仍然无法加载该类,则会抛出ClassNotFoundException异常。
通过这种双亲委派的机制,Java实现了类加载的层次结构。它可以确保类的加载是有序的,避免了重复加载和类的冲突。同时,它也提供了一种安全机制,防止恶意代码的加载和执行。
为什么要打破双亲委派机制以及如何打破
-
为什么要打破
有时我们需要多次加载同名目录下的类,比如:当我们在Tomcat上部署多个服务时,不同服务上可能依赖了不同版本的第三方jar,如果此时使用双亲委派机制加载类,会导致多个服务中第三方jar只加载一次,其他服务中的其他版本jar将不会生效,导致请求结果异常。为了避免这种情况,我们需要打破双亲委派机制,不再让父类[应用类加载器]加载,而是为每个服务创建自己的子类加载器。
-
如何打破
要打破双亲委派机制,可以自定义类加载器,并重写 ClassLoader 类中的 loadClass(String name, boolean resolve) 方法(或者是 findClass(String name) 方法,根据具体需求)。自定义的类加载器可以先尝试加载类,而不是直接委派给父加载器。
下面是一个简化的示例,说明如何自定义类加载器以打破双亲委派模型:
public class CustomClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 首先, 检查请求的类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 尝试自己加载类,而不是委派给父类加载器
c = findClass(name);
} catch (ClassNotFoundException e) {
// 如果自己无法加载类,那么调用父类加载器尝试加载
c = super.loadClass(name);
}
}
return c;
}@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 在这里加入具体的类加载逻辑,比如从文件系统中读取.class文件的字节流
// byte[] classBytes = …;
// return defineClass(name, classBytes, 0, classBytes.length);
// 示例中没有具体实现,因为它通常需要读取文件或其他数据源中的类数据
throw new ClassNotFoundException();
}
}
在这个例子中,findClass(String name) 方法被重写用于尝试加载类。如果在 findClass 中没有找到类,则会抛出 ClassNotFoundException 异常,然后调用父类加载器尝试加载。注意,直接破坏双亲委派机制可能会导致各种问题,如类冲突、安全问题等。因此,在实际开发中,只有在真正需要时才应该打破双亲委派模型,并且必须非常小心地实现。
自定义类加载器可以用在很多场景中,例如热部署(hot deploy)一个正在运行的应用程序,这通常需要动态地加载和卸载类。在框架开发中,比如OSGI、JSP的servlet容器等,这样的需求也是很常见的。


