虽然双亲委派模型是 Java 类加载机制的推荐实现方式,但在某些情况下,为了实现特定的功能,可能需要打破双亲委派模型。以下是一些常见的打破双亲委派模型的方法和场景:
1. 重写 loadClass
方法 (不推荐):
-
原理:
java.lang.ClassLoader
的loadClass
方法实现了双亲委派模型的逻辑。默认情况下,loadClass
方法会先检查类是否已经被加载过,如果没有,则委托给父类加载器加载。如果父类加载器无法加载,则调用findClass
方法尝试自己加载。 -
打破方式: 重写
loadClass
方法,不遵循双亲委派模型的逻辑,而是直接调用findClass
方法尝试自己加载类,或者自定义加载逻辑。 -
示例:
public class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // 不再委托给父类加载器,直接尝试自己加载 return findClass(name); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 实现自定义的类加载逻辑 // ... } }
-
缺点:
- 破坏了双亲委派模型的优点: 可能会导致类的重复加载、核心类库被篡改等问题。
- 容易出错: 需要自己处理类加载的各种细节,容易出错。
- 不推荐: 除非有非常特殊的需求,否则不建议重写
loadClass
方法。
2. 使用线程上下文类加载器 (Thread Context ClassLoader):
- 原理:
- 每个线程都有一个关联的上下文类加载器 (Context ClassLoader)。
- 可以通过
Thread.currentThread().getContextClassLoader()
获取当前线程的上下文类加载器。 - 可以通过
Thread.currentThread().setContextClassLoader(ClassLoader cl)
设置当前线程的上下文类加载器。 - 如果没有设置,线程上下文类加载器默认是应用程序类加载器 (AppClassLoader)。
- 打破方式:
- 在需要打破双亲委派模型的地方,获取当前线程的上下文类加载器,并使用它来加载类。
- 例如,JDBC 驱动程序通常由应用程序类加载器加载,但 JDBC API (如
java.sql.DriverManager
) 需要能够加载这些驱动程序。DriverManager
会使用线程上下文类加载器来加载驱动程序。
- 应用场景:
- SPI (Service Provider Interface): Java 中的 SPI 机制(例如 JDBC、JNDI、JAXP 等)通常使用线程上下文类加载器来加载服务提供者的实现类。
- 框架和容器: 一些框架和容器(例如,Tomcat、Spring)会使用线程上下文类加载器来加载应用程序的类或资源。
- 示例:
// 获取当前线程的上下文类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// 使用上下文类加载器加载类
try {
Class<?> myClass = contextClassLoader.loadClass("com.example.MyClass");
// ...
} catch (ClassNotFoundException e) {
// ...
}
3. 自定义类加载器并重写 findClass
方法 (推荐):
- 原理:
- 继承
java.lang.ClassLoader
,并重写findClass
方法。 - 在
findClass
方法中实现自己的类加载逻辑,例如从特定的目录、URL 或数据库中加载类。
- 继承
- 优点:
- 可以灵活地控制类的加载过程。
- 可以实现一些特殊的功能,例如热部署、模块化等。
- 示例: (前面已经提供过,此处不再重复)
4. OSGi (Open Service Gateway initiative):
- 原理:
- OSGi 是一个模块化系统,每个模块(bundle)都有自己的类加载器。
- OSGi 的类加载器模型不是严格的双亲委派模型,而是更复杂的网状结构。
- OSGi 允许模块显式地声明它们依赖的其他模块,以及它们导出的包。
- OSGi 框架会根据这些声明来解析模块之间的依赖关系,并加载相应的类。
- 打破方式: OSGi 的类加载器模型本身就不是双亲委派模型。
5. Tomcat 的类加载器:
- 原理:
- Tomcat 为了实现 Web 应用程序之间的隔离,以及共享库的加载,使用了多个类加载器。
- Tomcat 的类加载器结构:
Bootstrap
: 加载 JVM 启动所需的类。System
: 加载 Tomcat 自身的类。Common
: 加载 Tomcat 和所有 Web 应用程序都可以访问的类 (例如, JDBC 驱动)。WebappX
: 每个 Web 应用程序都有一个独立的WebAppClassLoader
,负责加载该应用程序的类 (位于WEB-INF/classes
和WEB-INF/lib
目录下).Shared
: (可选) 存放所有web应用共享的类.
- 加载顺序:
Bootstrap
System
Common
WebappX
(先在WEB-INF/classes
中查找, 再在WEB-INF/lib
中查找,最后才委托给父类加载器).Shared
(如果配置了的话)
- 打破双亲委派:
WebAppClassLoader
会优先在自己的范围内查找类,而不是立即委托给父类加载器, 这打破了双亲委派模型。- 这样做的好处是:
- 隔离性: 不同的 Web 应用程序可以使用不同版本的类库,而不会相互干扰。
- 灵活性: Web 应用程序可以覆盖 Tomcat 或共享库提供的类。
总结:
打破双亲委派模型的主要方法有:
- 重写
loadClass
方法 (不推荐)。 - 使用线程上下文类加载器 (常用于 SPI 机制)。
- 自定义类加载器并重写
findClass
方法 (推荐)。 - OSGi 模块化系统 (使用自定义的类加载器模型)。
- Tomcat 的类加载机制.