还是这张图,类加载器的入口是c++调用java代码创建了JVM启动器,个中的一个启动器是sun.misc.Launcher启动器。这个启动器启动并加载的AppClassLoader和ExtClassLoader。然后调用launcher.getClassLoader()方法获取loader工具, loader工具实质是一个ClassLoader,然后调用了ClassLoader的loadClass("...")方法加载类。也是在loadClass("...")方法里实现了双亲委派机制。
详细事理参考文章:https://www.cnblogs.com/ITPower/p/15363400.html
二、自定义类加载器剖析对付类加载器, 我们知道他的重点是loadClass(...)方法, 里面的双亲委派机制也是在loadClass方法里面实现的. loadClass方法里面实际上去加载类的是findClass()方法. 对付我们自定义的类加载器来说须要做到两点即可
这个自定义的类加载器继续自ClassLoader这个类加载器要重写ClassLoader类中的findClass()方法
其余我们还可以参考AppClassLoader和ExtClassLoader来写。
三、自定义类加载器实现下面我自己定义了一个类加载器
第一步:自定义类加载器继续自ClassLoader抽象类,然后定义一个布局方法, 用来吸收要加载的类名第二步:重写核心方法findClass(String name)这里有两步操作,
第一个是: 从类路径中读取要加载类的文件内容, 自定义
第二个是: 调用布局类的方法, 调用的系统的defineClass
接下来看看自定义的loadByte是如何实现的
这里的实现便是找到类, 并且将类的内容读取出来, 转换成二进制的字节码, 返回
末了一部分便是如何调用了.
用类加载器加载类, 然后实例化, 利用反射机制调用User1 的方法sout
package com.lxl.jvm;public class User1 { public void sout() { System.out.println("进入到User1"); }}
这里面System.out.println(clazz.getClassLoader().getClass().getName()); 获取当前类的类加载器, 猜一猜这里打印的会是谁?
看到了么? 是AppClassLoader, 为什么呢?
缘故原由是我的项目里已经有一个类User1了
我们自定义类加载器的父类是AppClassLoader. 而程序代码中的User1刚好是被AppClassLoader加载, 由于找到了,以是就不会再去我们指定的文件夹中查找了
这便是类的双亲委派机制的特点.
那么如果我们将项目中的User1类删除掉, 这是类加载器是谁呢? 当然便是我们自定义的类加载器了.
那么问题来了, 自定义类加载器的父类为什么是AppClassLoader呢?
四. 剖析自定义类加载的父类为什么是appClassLoader?我们来看一下源码
我们自定义的类加载器, 继续自ClassLoader类加载器, 那么在调用自定义类加载器的布局方法之前, 该当先加载父类ClassLoader的无参布局函数.
首先会实行ClassLoader的无参的布局方法.
而无参的布局方法会调用自身的布局方法
里面有一个parent, 我们便是要看看这个parent到底是谁呢. 来看看getSystemClassLoader()方法
之前我们已经研究过getClassLoader()这个方法了, 这里面定义的loadClass是谁呢?便是AppClassLoader.
这便是为什么自定义class类加载器的父类是AppClassLoader的缘故原由了。
五、冲破双亲委派机制首先,我们要明白,什么是双亲委派机制?为什么要冲破双亲委派机制?什么时候须要冲破双亲委派机制?
1. 什么是双亲委派机制?在前面,我们说了什么是双亲委派机制,子类委托父类加载的这个逻辑,便是双亲委派机制。如果还不知道什么是双亲委派机制,可以查看文章:https://www.cnblogs.com/ITPower/p/15363400.html
2. 如何冲破双亲委派机制呢?我们知道了,双亲委派机制便是类在加载的时候,从自定义类加载器开始查找是否已经加载过这个类,如果没有加载过则加载类,但是不是由自己急速加载,而是委托上级加载。到了上级,先查找,找不到在加载,然后也不是自己急速加载,依次类推。。。。这便是双亲委派机制,要冲破双亲委派机制,那么便是不让他委托上级类加载器加载,由自己来加载。那么如何实现呢?
比如, 我现在有一个自定义类加载器, 加载的是~/com/lxl/jvm/User1.class类, 而在运用程序的target目录下也有一个com/lxl/jvm/User1.class, 那么, 终极User1.class这个类将被哪个类加载器加载呢? 根据双亲委派机制, 我们知道, 他一定是被运用程序类加载器AppClassLoader加载, 而不是我们自定义的类加载器, 为什么呢? 由于他要向上探求, 向下委托. 当找到了往后, 便不再向后实行了.
我们要冲破双亲委派机制, 便是要让自定义类加载器来加载我们的User1.class, 而不是运用程序类加载器来加载
双亲委派机制是在ClassLoader类的loadClass(...)方法实现的. 如果我们不想利用系统自带的双亲委派模式, 只须要重新实现ClassLoader的loadClass(...)方法即可. 下面是ClassLoader中定义的loadClass()方法. 里面实现了双亲委派机制
下面给DefinedClassLoaderTest.java增加一个loadClass方法, 拷贝上面的代码即可. 删除掉中间实现双亲委派机制的部分
这里须要把稳的是, com.lxl.jvm是自定义的类包, 只有我们自己定义的类才从这里加载. 如果是系统类, 依然利用双亲委派机制来加载.
来看看运行结果:
调用了user1的sout方法com.lxl.jvm.DefinedClassLoaderTest
现在User1方法确实是由自定义类加载器加载的了
源码:
package com.lxl.jvm;import java.io.FileInputStream;import java.lang.reflect.Method;/ 自定义的类加载器 /public class DefinedClassLoaderTest extends ClassLoader{ private String classPath; public DefinedClassLoaderTest(String classPath) { this.classPath = classPath; } / 重写findClass方法 如果不会写, 可以参考URLClassLoader中是如何加载AppClassLoader和ExtClassLoader的 @param name @return @throws ClassNotFoundException / @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadBytes(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] loadBytes(String name) throws Exception { // 我们须要读取类的路径 String path = name.replace('.', '/').concat(".class"); //String path = ""; // 去路径下查找这个类 FileInputStream fileInputStream = new FileInputStream(classPath + "/" + path); int len = fileInputStream.available(); byte[] data = new byte[len]; fileInputStream.read(data); fileInputStream.close(); return data; } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { / 直接实行findClass()...什么意思呢? 首先会利用自定义类加载器加载类, 不在向上委托, 直接由 自己实行 jvm自带的类还是须要由勾引类加载器自动加载 / if (!name.startsWith("com.lxl.jvm")) { c = this.getParent().loadClass(name); } else { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } public static void main(String[] args) throws Exception { DefinedClassLoaderTest classLoader = new DefinedClassLoaderTest("/Users/luoxiaoli"); Class<?> clazz = classLoader.loadClass("com.lxl.jvm.User1"); Object obj = clazz.newInstance(); Method sout = clazz.getDeclaredMethod("sout", null); sout.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); }}
六. 冲破双亲委派机制案例--tomcat支配多运用?1. tomcat为何要冲破双亲委派机制?
常日,我们在做事器安装的一个tomcat下会支配多个运用。而这多个运用可能利用的类库的版本是不同的。比如:项目A利用的是spring4,项目B利用的是Spring5。Spring4和Spring5多数类都是一样的,但是有个别类有所不同,这些不同是类的内容不同,而类名,包名都是一样的。如果,我们采取jdk向上委托的办法,项目A在支配的时候,运用类加载器加载了他的类。在支配项目B的时候,由于类名相同,这是运用做事器就不会再次加载同包同名的类。这样就会有问题。以是, tomcat须要冲破双亲委派机制。不同的war包下的类自己加载,而不向上委托。根本类依然向上委托。
2.tomcat是如何冲破双亲委派机制的?实际上, 我们的tomcat可以加载各种各样类型的war包, 相互之间没有影响. 由于tomcat冲破了双亲委派机制, 下面我们就来看看tomcat是如何冲破双亲委派机制的?
如上图, 上面的橙色部门还是和原来一样, 采取双亲委派机制. 而黄色部分是tomcat第一部分自定义的类加载器, 这部分紧张是加载tomcat包中的类, 这一部分依然采取的是双亲委派机制, 而绿色部分是tomcat第二部分自定义类加载器, 正事这一部分, 冲破了类的双亲委派机制. 先面我们就来详细看看tomcat自定义的类加载器
1. tomcat第一部分自定义类加载器(黄色部分)这部分类加载器, 在tomcat7及以前是tomcat自定义的三个类加载器, 分别加载不同文件家下的jar包. 而到了tomcat7及往后, tomcat将这三个文件夹合并了, 合并成了一个lib包. 也便是我们现在看到的lib包
我们来看看这三个类加载器的紧张功能.
commonClassLoader: tomcat最基本的类加载器, 加载路径中的class可以被tomcat容器本身和各个webapp访问;catalinaClassLoader: tomcat容器中私有的类加载器, 加载路径中的class对付webapp不可见的部分。sharedClassLoader: 各个webapps共享的类加载器, 加载路径中的class对付所有的webapp都可见, 但是对付tomcat容器不可见.这一部分类加载器, 依然采取的是双亲委派机制, 缘故原由是, 他只有一份. 如果有重复, 那么也因此这一份为准. 这部分紧张加载的是tomcat自带的类。
2.tomcat第二部分自定义类加载器(绿色部分)绿色部分是java项目在打war包的时候, tomcat自动天生的类加载器, 也便是说 , 每一个项目打成一个war包, tomcat都会自动天生一个类加载器, 专门用来加载这个war包. 而这个类加载器冲破了双亲委派机制. 我们可以想象一下, 如果这个webapp类加载器没有冲破双亲委派机制会怎么样?
之前也说过,如果没有冲破, 他就会委托父类加载器去加载, 一旦加载到了, 子类加载器就没有机会在加载了. 那么, spring4和spring5的项目想共存, 那是不可能的了.
以是, 这一部分他冲破了双亲委派机制
这样一来, webapp类加载器不须要在让上级去加载, 他自己就可以加载对应war里的class文件. 当然了, 其他的根本项目文件, 还是要委托上级加载的.
下面我们来实现一个自定义的tomcat类加载器
3.自定义tomcat的war包类加载器如何冲破双亲委派机制, 我们在上面已经写过一个demo了.
那么, 现在我有两个war包, 分处于不同的文件夹, tomcat如何利用各自的类加载器加载自己包下的class类呢?
我们来举个例子, 比如: 在我的home目录下有两个文件夹, tomcat-test和tomcat-test1. 用这两个文件夹来仿照两个项目.
在他们的下面都有一个com/lxl/jvm/User1.class
虽然类名和类路径都是一样的,但是他们的内容是不同的
这个时候,如果tomcat要同时加载这两个目录下的User1.class文件, 我们如何操作呢?
实在,非常大略, 按照上面的思路, tomcat只须要为每一个文件夹天生一个新的类加载器就可以了.
public static void main(String[] args) throws Exception { // 第一个类加载器 DefinedClassLoaderTest classLoader = new DefinedClassLoaderTest("/Users/app/tomcat-test"); Class<?> clazz = classLoader.loadClass("com.lxl.jvm.User1"); Object obj = clazz.newInstance(); Method sout = clazz.getDeclaredMethod("sout", null); sout.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); // 第二个类加载器 DefinedClassLoaderTest classLoader1 = new DefinedClassLoaderTest("/Users/app/tomcat-test1"); Class<?> clazz1 = classLoader1.loadClass("com.lxl.jvm.User1"); Object obj1 = clazz1.newInstance(); Method sout1 = clazz1.getDeclaredMethod("sout", null); sout1.invoke(obj1, null); System.out.println(clazz1.getClassLoader().getClass().getName()); }
他们都是只加载自己目录下的文件. 我们来看看实行结果:
调用了user1的sout方法com.lxl.jvm.DefinedClassLoaderTest调用了其余一个项目user1的sout方法, 他们是不同的com.lxl.jvm.DefinedClassLoaderTest
虽然上面的代码很大略,但这便是tomcat加载不同war包的事理。不同的是,tomcat实现逻辑会更繁芜,他的类加载器都是动态天生的。精髓都是一样的。
4. 思考: tomcat自定义的类加载器中, 有一个jsp类加载器,jsp是可以实现热支配的, 那么他是如何实现的呢?jsp实在是一个servlet容器, 由tomcat加载. tomcat会为每一个jsp天生一个类加载器. 这样每个类加载器都加载自己的jsp, 不会加载别人的. 当jsp文件内容修正时, tomcat会有一个监听程序来监听jsp的改动. 比如文件夹的修正韶光, 一旦韶光变了, 就重新加载文件夹中的内容.
详细tomcat是怎么实现的呢? tomcat自定义了一个thread, 用来监听不同文件夹中文件的内容是否修正, 如何监听呢? 就看文件夹的update time有没有变革, 如果有变革了, 那么就会重新加载.
jsp热支配也不是急速就会看到效果,其他他也是有延迟的,这个延迟便是重新加载的过程。
来源:https://www.cnblogs.com/ITPower/p/15374926.html