4.自定义类加载器实现及在tomcat中的应用
了解了类加载器的双亲委派机制, 也知道了双亲委派机制的原理,接下来就是检验我们学习是否扎实了,来自定义一个类加载器
一. 回顾类加载器的原理
还是这张图,类加载器的入口是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热部署也不是立刻就会看到效果,其他他也是有延迟的,这个延迟就是重新加载的过程。
4.自定义类加载器实现及在tomcat中的应用的更多相关文章
- JVM自定义类加载器加载指定classPath下的所有class及jar
一.JVM中的类加载器类型 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器. 1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责 ...
- Tomcat实现自定义类加载器
什么是类加载器? 这是官方给的定义 在 Java 虚拟机的实现中,初始类可以作为命令行参数提供. 或者,该实现可以提供一个初始类,该类设置一个类加载器,该类加载器依次加载应用程序. 初始类的其他选择也 ...
- Java内存管理-掌握自定义类加载器的实现(七)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoa ...
- java自定义类加载器
前言 java反射,最常用的Class.forName()方法.做毕设的时候,接收到代码字符串,通过 JavaCompiler将代码字符串生成A.class文件(存放在classpath下,也就是ec ...
- Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论
Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...
- java类加载器学习2——自定义类加载器和父类委托机制带来的问题
一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...
- Java自定义类加载器与双亲委派模型
其实,双亲委派模型并不复杂.自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后copy一下就能用.但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行.可是自定义类 ...
- (转)JVM——自定义类加载器
背景:为什么要自定义,如何自定义,实现过程 转载:http://blog.csdn.net/SEU_Calvin/article/details/52315125 0. 为什么需要自定义类加载器 网上 ...
- jvm(1)类的加载(二)(自定义类加载器)
[深入Java虚拟机]之四:类加载机制 1,从Java虚拟机的角度,只存在两种不同的类加载器: 1,启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有其他 ...
随机推荐
- WPF---数据绑定之ValidationRule数据校验(六)
一.概述 我们知道,Binding好比架设在Source和Target之间的桥梁,数据可以借助这个桥梁进行流通.在数据流通的过程中,我们可以在Binding这座桥梁上设置关卡,对数据的有效性进行验证. ...
- 用Java8把List转为Map
1 import com.yang.test.User; 2 3 import javax.jws.soap.SOAPBinding; 4 import java.util.*; 5 import j ...
- Python代码阅读(第1篇):列表映射后的平均值
本篇阅读的代码实现了将列表进行映射,并求取映射后的平均值. 本篇阅读的代码片段来自于30-seconds-of-python. average_by def average_by(lst, fn=la ...
- 面试必问题:JS防抖与节流
摘要:防抖与节流可谓是面试常见,其实很好理解,下面带你分分钟了解防抖与节流的基本思想与写法~ 本文分享自华为云社区<JS防抖与节流快速了解与应用>,作者:北极光之夜. . 一.速识防抖: ...
- Learning ROS: rqt_console和rqt_logger_level使用
rqt_console:操作.查看log信息 rqt_logger_level:设置log等级 打开node: rosrun rqt_console rqt_console rosrun rqt_lo ...
- JavaSE基础之Java中的数据类型
基本数据类型:8个 整数类型(4):byte short int long 浮点类型(2):float double 字符类型:char 布尔类型:boolean 引用数据类型:3个 数组 类:cla ...
- JavaScript高级程序设计读书笔记之JSON
JSON(JavaScript Object Notation)JavaScript对象表示法.JSON是JavaScript的一个严格的子集,利用了JavaScript中的一些模式来表示结构化数据. ...
- sublime text 的 Ctrl + P「模糊搜索算法」
Reverse Engineering Sublime Text's Fuzzy Match 这是我能 google 到的最早的一篇关于 Sublime Text 的模糊搜索的文章. https:// ...
- Java clone() 方法克隆对象——深拷贝与浅拷贝
基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...
- noip模拟36
\(\color{white}{\mathbb{荷花映日,莲叶遮天,名之以:残荷}}\) 今天再次翻车掉出前十 开题看错 \(t1\) 以为操作2的值固定发现是个简单题,然后 \(t2\) 开始大力 ...