本文 首发于 🍀 永浩转载 请注明 来源

双亲委派机制

一、双亲委派机制

  1. class文件是通过类加载器加载到jvm中的

  2. 为了防止内存中存在多份同样的字节码,使用了双亲委派机制(不会自己加载类,而是把请求委托给父加载器去完成,依次向上)

  3. jdk本地方法类一般有根加载器(BootStrap Loader) 装载,jdk内部实现的扩展类一般由扩展加载器(ExtClassLoader),程序中的类文件则有系统加载器(AppClassLoader)装载

二、如何打破双亲委派机制

  1. 只要我加载类的时候,不是从AppClassLoader→ExtClassLoader→BootStrap Loader这个顺序找,那就是打破了。
  2. 因为加载class核心的方法在LoaderClass类的loadClass()方法上(双亲委派机制的核心实现上)
  3. 只要我自定义个ClassLoader,重写loadClass方法(不依照往上开始寻找类加载器),那就算是打破双亲委派机制了。

三、破坏双亲委派机制的场景

  1. tomcat:初学部署项目时,我们是把war包放到tomcat的webapp下,意味着tomcat可以运行多个web应用程序
  2. 那假设我现在有2个web应用程序,都有一个类,叫做User,并且它们的类全限定名都一样,比如:都是com.xxxxx.User。但是它们的具体实现是不一样的
  3. 那么tomcat是如何保证它们不会冲突的呢?
  4. 答案就是:tomcat给每个web应用创建了一个类加载器实例(WebAppClassLoader),该加载器重写了loadClass方法,优先加载当前应用目录下的类,如果当前找不到,才一层一层往上找。这样就做到了web应用层级的隔离。

四、tomcat还有别的类加载器吗

  1. 并不是web应用下的所有依赖都是需要隔离的,比如redis就是可以web应用之间共享的

  2. 因为如果版本相同,没必要每个web应用都独自加载一份

  3. 做法很简单,tomcat就在WebAppClassLoader上加了个父类加载器(SharedClassLoader),如果WebAppClassLoader自身没有加载到某个类,那就委托ShaerClassLoader去加载。(无非就是把需要应用程序之间需要共享的类放到一个共享目录下,SharedClassLoader)读共享目录的类就好了

  4. 为了隔离web应用与tomcat本身的类,又有类加载器(CatalinaClassLoader)来装载tomcat本身的依赖

  5. 如果tomcat本身的类的依赖和web应用还需要共享,那么还有类加载器(CommonClassLoader)来装载进而达到共享

  6. 各个类加载器的加载目录可以到tomcat的catalina.properties配置文件上查看

五、jdbc

  1. 有没有破坏双亲委派机制,见仁见智
  2. jdbc定义类接口,具体实现类由各个厂商进行实现(比如Mysql)
  3. 类加载有个规则:如果一个类由类加载器A加载,那么这个类的依赖类也是有相同的类加载器加载
  4. 我们用jdbc的时候,是使用DriverManager进而获取Connection,DriverManager在java.sql包下,显然是有BootStrap类加载器进行装载
  5. 当我们使用DriverManager.getConnection()时,得到的是一定是厂商实现的类
  6. 但BootStrap 加载器显然不可以加载各个厂商实现的类,这些实现类又没在java包中,怎么可能加载到呢
  7. DriverManager的解决方案是:在DriverManager初始化时,得到上下文加载器,去获取Connection时,是使用上下文加载器去加载Connection的,而这里的线程上下文加载器实际上还是(AppClassLoader)
  8. 在获取Connection的时候,还是先找到ExtClassLoader和B o o t S t ra p C la s sLoader,只不过这两加载器肯定是加载不到的,最终会有AppClassLoader进行加载
  9. 那这种情况,有的人觉得破坏了双亲委派机制,因为本来明明应该是有BootStrapClassLoader进行加载的,结果你来了一手线程上下文加载器,改掉了类加载器
  10. 有的人觉得没破坏双亲委派机制,只是改成了由线程上下文加载器进行类加载,但是还是遵守依次往上找父类加载,都找不到时才由自身加载。认为原则上没有改变。
  11. 我觉得这不重要,重要的是弄懂底层原理