(二)打破双亲委派?

Posted by Steven on 2021-05-12
Estimated Reading Time 4 Minutes
Words 1.2k In Total
Viewed Times

上篇文章中说到,双亲委派是在ClassLoader类的loadClass()方法中实现的,因此想打破,那就是

  1. 重写loadClass()

  2. 打破过双亲委派机制的情况(周志明写的《深入理解java虚拟机》中有说)

    • JDK1.2之前,自定义ClassLoader都必须重写loadClass()方法

    • 可以在一个线程设定自己线程上下文的ClassLoader
      ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContentClassLoader指定
      image-20210514183843994

    • 热启动,热部署
      osgitomcat都有自己的模块指定ClassLoader(可以加载同一类库的不同版本)

      写一个web应用,扔到一个Tomcat指定的目录下,启动之后类加载进来了,但是如果有两个不同的web应用,是可以同时被加载的,如果web应用01加载了一个类叫A(v1),web应用01也加载了一个类A(v2), A的版本不同,这两个类的名字是一样的。这就已经打破了双亲委派机制

      如果用双亲委派机制,这两个A肯定不会都加载到一个空间

      所以Tomcat中,每一个web应用都有自己的ClassLoader

      Tomcat为什么要破坏双亲委派模型?

      每个Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器

      事实上,tomcat之所以造了一堆自己的classLoader,大致是出于下面三个目的:

      • 对于各个webapp中的classlib,需要相互隔离,不能出现一个应用加载的类库会影响另一个应用的情况,而对于许多应用,需要共享的lib以便不浪费资源。
      • jvm一样的安全性问题。使用单独的classloader去装载tomcat自身的类库,以免其他恶意或无意的破坏。
      • 热部署。
    • JDBC
      那么JDBC为什么要破坏双亲委派模型?

      因为类加载器收到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。

      JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于$JAVA_HOME中jre/lib/rt.java包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的jar包,

      根据类加载机制,当被状态的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。

      这就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。
      我们知道,BootStrap类加载器默认只负责加载$JAVA_HOME中的jre/lib/rt.jar里的所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。

这个例子演示只是重写findClass()是不能打破双亲委派机制的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.shifpeng.scaffold.service.jvm;

/**
* @Project: scaffold
* @Description:
* @Author: Steven
**/
public class ClassReloading01 {
public static void main(String[] args) throws Exception {
CustomizeClassLoader customizeClassLoader = new CustomizeClassLoader();
Class clazz = customizeClassLoader.loadClass("com.shifpeng.scaffold.service.jvm.dao");

customizeClassLoader = null;
System.out.println(clazz.hashCode());

customizeClassLoader = null;

customizeClassLoader = new CustomizeClassLoader();
Class clazz1 = customizeClassLoader.loadClass("com.shifpeng.scaffold.service.jvm.dao");
System.out.println(clazz1.hashCode());

System.out.println(clazz == clazz1);
//输入 true
}

}

打破双亲委派的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.shifpeng.scaffold.service.jvm;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* @Project: scaffold
* @Description:
* @Author: Steven
**/
public class ClassReloading02 {

// 定义自己的新的ClassLoader,继承ClassLoader
private static class MyLoader extends ClassLoader {

//
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
//首先去找要load的文件,如果没有找到,就让父亲去load,如果找到了就自己load
//我这里没去找是不是加载过,反正只要拿到类明,我就去加载
//如果要加载同一个Class是覆盖不了的,所以我直接把ClassLoader干掉就行了
File f = new File("/Users/Steven/test/" + name.replace(".", "/").concat(".class"));

if (!f.exists()) return super.loadClass(name);
try {
InputStream is = new FileInputStream(f);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}

return super.loadClass(name);
}
}

//所以tomcat就是这么干的,直接把web应用的整个ClassLoader干掉,重新再加载一遍
public static void main(String[] args) throws Exception {
MyLoader m = new MyLoader();
Class clazz = m.loadClass("com.shifpeng.scaffold.service.jvm.dao.Hello");

//重新new MyLoader
m = new MyLoader();
Class clazzNew = m.loadClass("com.shifpeng.scaffold.service.jvm.dao.Hello");

System.out.println(clazz == clazzNew);

//返回
//false
}

}

因此热部署就是这样:

动态改了之后重新加载,我重新加调用一下loadClass()就可以了


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !