java 详解类加载器的双亲委派及打破双亲委派
https://blog.csdn.net/chang_ge/article/details/80262115
/www.jb51.net/article/102920.htm
https://www.cnblogs.com/wxd0108/p/6681618.html
其实,双亲委派模型并不复杂。自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后copy
一下就能用。但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行。可是自定义类加载器又不经常用,时间久了容易忘记。相信你经常会记不太清loadClass
、findClass
、defineClass
这些函数我到底应该重写哪一个?它们主要是做什么的?本文大致分析了各个函数的流程,目的就是让你看完之后,难以忘记!或者说,延长你对自定义类加载器的记忆时间!随时随地想自定义就自定义!
1. 双亲委派模型
关于双亲委派模型,网上的资料有很多。我这里只简单的描述一下,就当是复习。
1.1 什么是双亲委派模型?
首先,先要知道什么是类加载器。简单说,类加载器就是根据指定全限定名称将class
文件加载到JVM
内存,转为Class
对象。如果站在JVM
的角度来看,只存在两种类加载器:
启动类加载器(
Bootstrap ClassLoader
):由C++
语言实现(针对HotSpot
),负责将存放在<JAVA_HOME>lib
目录或-Xbootclasspath
参数指定的路径中的类库加载到内存中。其他类加载器:由
Java
语言实现,继承自抽象类ClassLoader
。如:
- 扩展类加载器(
Extension ClassLoader
):负责加载<JAVA_HOME>libext
目录或java.ext.dirs
系统变量指定的路径中的所有类库。- 应用程序类加载器(
Application ClassLoader
)。负责加载用户类路径(classpath
)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException
),子加载器才会尝试自己去加载。
1.2 为什么需要双亲委派模型?
为什么需要双亲委派模型呢?假设没有双亲委派模型,试想一个场景:
黑客自定义一个
java.lang.String
类,该String
类具有系统的String
类一样的功能,只是在某个函数稍作修改。比如equals
函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM
中。此时,如果没有双亲委派模型,那么JVM
就可能误以为黑客自定义的java.lang.String
类是系统的String
类,导致“病毒代码”被执行。
而有了双亲委派模型,黑客自定义的java.lang.String
类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String
类,最终自定义的类加载器无法加载java.lang.String
类。
或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String
类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM
中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。
举个简单例子:
ClassLoader1
、ClassLoader2
都加载java.lang.String
类,对应Class1、Class2对象。那么Class1
对象不属于ClassLoad2
对象加载的java.lang.String
类型。
二、打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:
①定义Test类。
1
2
3
4
5
|
public class Test { public Test(){ System.out.println( this .getClass().getClassLoader().toString()); } } |
②重新定义一个继承ClassLoader的TestClassLoaderN类,这个类与前面的TestClassLoader类很相似,但它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。
public
class
TestClassLoaderN
extends
ClassLoader {
private
String name;
public
TestClassLoaderN(ClassLoader parent, String name) {
super
(parent);
this
.name = name;
}
@Override
public
String toString() {
return
this
.name;
}
@Override
public
Class<?> loadClass(String name)
throws
ClassNotFoundException {
Class<?> clazz =
null
;
ClassLoader system = getSystemClassLoader();
try
{
clazz = system.loadClass(name);
}
catch
(Exception e) {
// ignore
}
if
(clazz !=
null
)
return
clazz;
clazz = findClass(name);
return
clazz;
}
@Override
public
Class<?> findClass(String name) {
InputStream is =
null
;
byte
[] data =
null
;
ByteArrayOutputStream baos =
new
ByteArrayOutputStream();
try
{
is =
new
FileInputStream(
new
File(
"d:/Test.class"
));
int
c =
0
;
while
(-
1
!= (c = is.read())) {
baos.write(c);
}
data = baos.toByteArray();
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
try
{
is.close();
baos.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
return
this
.defineClass(name, data,
0
, data.length);
}
public
static
void
main(String[] args) {
TestClassLoaderN loader =
new
TestClassLoaderN(
TestClassLoaderN.
class
.getClassLoader(),
"TestLoaderN"
);
Class clazz;
try
{
clazz = loader.loadClass(
"test.classloader.Test"
);
Object object = clazz.newInstance();
}
catch
(Exception e) {
e.printStackTrace();
}
}
}