ClasssLoader 入门了解
概念
类加载就是用来加载类到虚拟机中。
Java源程序通过编译之后,生成java字节码文件(.class文件),类加载器负责读取(loadClass())加载字节码文件,转换成一个
java.lang.Class
实例。
类加载器双亲委派模型
为什么要使用双亲委派模型:避免类的重复加载
防止内存中出现多份同样的字节码
比如BootstrapClassLoader 已经加载过
java.lang.String
,现在我们自己在classpath下由创建了一个java.lang.String
,这个时候类加载的时候首先会去调用BootstrapClassLoader 去加载rt.jar下的java.lang.String
,而不会加载classpath下的java.lang.String
。避免类重复加载,保证安全性
假设上面的String加载的是我们ClassPath下的
java.lang.String
,比如我们调用String.equals()方法的时候,在里面加一些入侵代码,然后类在加载的时候加载到jvm中了,这样就会导致入侵代码执行,造成安全隐患。
JDK已有的类加载器
- BootStrap Classloader 启动类加载器 C++编写 主要加载
rt.jar
并不继承ClassLoader - Extension Classloader 拓展类加载器 加载
%JAVA_HOME%/lib/ext/*.jar
- AppClassLoader 应用类加载器或者说是系统类加载器 可以通过
ClassLoader.getSystemClassLoader()
来获取它。 加载路径是从Classpath
下加载
自定义加载器加载路径是完全自定义的
自定义类加载器
/**
* Created on 2018/5/10
*
* @author wang.teng
*/
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
super();//让系统类加载器成为该类的父加载器
this.path = path;
}
public MyClassLoader(ClassLoader parent, String path) {
super(parent);
this.path = path;
}
/**
* 跟据名称加载到相应的类
* eg:com.wt.classloader
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = readClassForByte(name);
return this.defineClass(name, b, 0, b.length);
}
private byte[] readClassForByte(String name) {
name =name.replaceAll("\\.","/");
String path = this.path + name + ".class";
InputStream is = null;
ByteArrayOutputStream bos = null;
File file = new File(path);
try {
is = new FileInputStream(file);
bos = new ByteArrayOutputStream();
int tmp = 0;
while ((tmp = is.read()) != -1) {
bos.write(tmp);
}
return bos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
测试方法:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader("J://tmp//");
// MyClassLoader myClassLoader = new MyClassLoader(null,"J://tmp//");
//com.wt.classloader.Demo
Class<?> c = myClassLoader.loadClass("com.wt.classloader.Demo");
c.newInstance();
}
如果不指定加载器,默认会加载AppClassLoader ,如存在同包名的类,那末会加载classpath下的的类,不会加载指定路径下的类。
第一种方式,编写一个Demo.java,没有包,编译成.class之后,放到
J:temp/
下,这时,加载Demo.class的方法是:MyClassLoader myClassLoader = new MyClassLoader("J://tmp//"); Class<?> c = myClassLoader.loadClass("Demo");
第二种,同样编写Demo.java ,这时加上包名
com.wt.classloader
,同样放入J:temp/
下,这时在我们的工作空间同样写上Demo.java 包也相同。MyClassLoader myClassLoader = new MyClassLoader("J://tmp//"); Class<?> c = myClassLoader.loadClass("com.wt.classLoader.Demo");
这时大家可以先猜猜看会加载
J:tmp//com/wt/classloader/Demo.class
,还是会加载Classpath:com.wt.classloader.Demo.class
。答案是后者,为什么呢?其实很简单,MyClassLoader的父类是AppClassLoader,而AppClassLoader会加载ClassPath下面的类,这时候就会去找com.wt.classloader.Demo.class
,这个时候我们包下面刚好有,那就会直接加载,加载完之后就返回,并不会再去加载J://tmp//
下的Demo.class。那么我们如何加载我们
J:tmp//
下的Demo.class呢,其实很简单,只需要设置其父类加载器为BootStrapClassLoader,因为再BootStrapClassLoader加载的包下是找不到com.wt.classLoader.Demo
的,找不到就会找加载我们所指定路径的class。
ClassLoader使用场景
- tomcat的框架 (WebappClassLoader 加载
webapps
下的class文件)