1.什么是双亲委派模型
双亲委派模型(Parents Delegation Model)默认的类加载逻辑是:一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
双亲委派机制的优点:
可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证全局唯一性
Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一
保护程序安全,防止类库的核心 API 被随意篡改
例如:在工程中新建 java.lang 包,接着在该包下新建 String 类,并定义 main 函数
public class String { public static void main(String[] args) { System.out.println("demo info"); } }此时执行 main 函数会出现异常,在类 java.lang.String 中找不到 main 方法。因为JVM的启动类加载器
Bootstrap ClassLoader会优先加载java.lang包下的原生String类,而用户自定义的String类会被忽略。
2.怎样打破双亲委派模型
双亲委派机制的缺点:检查类是否加载的委托过程是单向的,这个方式虽然从结构上看比较清晰,使各个 ClassLoader 的职责非常明确,但顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类(可见性)
双亲委派模型并不是一个具有强制性约束的模型,而是 Java 设计者推荐给开发者的类加载器实现方式
破坏双亲委派模型的方式:
自定义 ClassLoader
- 如果不想破坏双亲委派模型,只需要重写 findClass 方法
- 如果想要去破坏双亲委派模型,需要去**重写 loadClass **方法
- 双亲委派的逻辑在loadClass方法中,所以必须重写loadClass才能破坏机制,而findClass只是加载类的一种方式,不涉及委派逻辑。因此,正确的做法是覆盖loadClass,跳过父类加载器的调用,直接加载类。
引入线程上下文类加载器
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:
- SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的
- SPI 的实现类是由系统类加载器加载,引导类加载器是无法找到 SPI 的实现类,因为双亲委派模型中 BootstrapClassloader 无法委派 AppClassLoader 来加载类
JDK 开发人员引入了线程上下文类加载器(Thread Context ClassLoader),这种类加载器可以通过 Thread 类的 setContextClassLoader 方法进行设置线程上下文类加载器,在执行线程中抛弃双亲委派加载模式,使程序可以逆向使用类加载器,使 Bootstrap 加载器拿到了 Application 加载器加载的类,破坏了双亲委派模型
实现程序的动态性,如代码热替换(Hot Swap)、模块热部署(Hot Deployment)
IBM 公司主导的 JSR一291(OSGiR4.2)实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi 中称为 Bundle)都有一个自己的类加载器,当更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换,在 OSGi 环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构
当收到类加载请求时,OSGi 将按照下面的顺序进行类搜索:
- 将以 java.* 开头的类,委派给父类加载器加载
- 否则,将委派列表名单内的类,委派给父类加载器加载
- 否则,将 Import 列表中的类,委派给 Export 这个类的 Bundle 的类加载器加载
- 否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载
- 否则,查找类是否在自己的 Fragment Bundle 中,如果在就委派给 Fragment Bundle 类加载器加载
- 否则,查找 Dynamic Import 列表的 Bundle,委派给对应 Bundle 的类加载器加载
- 否则,类查找失败
热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为,热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。
注意:对于java核心类,还是要走双亲委派机制的,只有那些自己想打破双亲委派机制的类,采取走自定义的加载方法。
3.打破双亲委派模型的风险
- 安全性:可能加载未经授权或恶意的代码,破坏应用程序的安全性,
- 兼容性问题:自定义类加载器与标准Java库可能存在兼容性问题
- 维护负担:复杂的类加载逻辑需要额外的代码维护和管理
使用建议:只有在特定需求下才考虑打破这一机制,如实现自定义插件系统或支持模块化运行时功能。在构建严肃的应用程序时,务必仔细考虑潜在的安全风险和兼容性问题。