ClassLoader的作用
个人理解,ClassLoader用来从外部空间(硬盘、网络等)向jvm内存空间加载字节码文件,并将其解析为Class对象。
ClassLoader分类
ClassLoader大体可分为4类:BootstrapClassLoader、ExtClassLoader、AppClassLoader以及用户自定义的ClassLoader。
BootstrapClassLoader是JVM启动之后第一个被启动的类加载器,其默认加载 JAVE_HOME/jre/lib目录下的核心库;
同时,BootstrapClassLoader还会生成ExtClassLoader的实例,设置ExtClassLoader的parent为null。ExtClassLoader默认加载JAVE_HOME/jre/lib/ext目录下的扩展包;BootstrapClassLoader还会继续生成AppClassLoader实例,将其parent设置为ExtClassLoader。AppClassLoader默认加载CLASSPATH目录下的包。
由此可见,虽然AppClassLoader的parent是ExtClassLoader,但是AppClassLoader本身是由BootstrapClassLoader创建;虽然BootstrapClassLoader是ExtClassLoader的parent,但是在ExtClassLoader中parent被设置为null。这是因为BootstrapClassLoader是由C语言实现的,并不是一个java类。
双亲委派模式
ExtClassLoader --> BootstrapClassLoader
AppClassLoader --> ExtClassLoader
CustomClassLoader1 --> AppClassLoader
CustomClassLoader2 --> AppClassLoader
双亲委派模式:当加载一个class的时候,某个ClassLoader首先询问其父ClassLoader(如果存在的话)是否已经加载了该class:如果父ClassLoader已经加载了该class的话,就返回该class的引用;如果父ClassLoader也没有加载此class的话,则本ClassLoader则尝试加载,加载成功就返回引用,加载不成功就抛出ClassNotFoundException。
// 检查类是否已被装载过
Class c = findLoadedClass(name);
if (c == null ) {
// 指定类未被装载过
try {
if (parent != null ) {
// 如果父类加载器不为空, 则委派给父类加载
c = parent.loadClass(name, false );
} else {
// 如果父类加载器为空, 则委派给启动类加载加载
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
// 捕获, 并通过findClass方法, 由自身加载
c = findClass(name);
}
}
使用双亲委派模式,在一个项目内可以防止某个class被重复加载,同时可以防止我们自定义的类取代java的核心类。当然,在tomcat中,两个不同的web项目可以在maven中引用相同的class(不同的web项目之间相互不影响,class会被加载两次,这是因为tomcat的类加载机制为子优先模式【双亲委派模式为父优先】)。
自定义类加载器
java提供的类加载器只能加载特定目录下的jar和class,如果我们需要从其他位置加载类的时候,就需要自定义类加载器。
在实现自己的类加载器的时候,只需要继承java.lang.ClassLoader类并重写findClass方法(ClassLoader中loadClass方法在找不到类的时候就会调用findClass方法去查找该类,因此一般只需要重写findClass方法)。以下是通过自定义类加载器加载位于桌面上的Test类,并调用其hello()方法,该方法输出“hello world”字符串
package my.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class CustomClassLoader extends ClassLoader {
private String baseUrl;
public CustomClassLoader(ClassLoader parent, String baseUrl) {
super(parent);
this.baseUrl = baseUrl;
}
public CustomClassLoader(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
byte[] data = getClassByteData(name);
if (data == null){
throw new ClassNotFoundException();
}
clazz = defineClass(name,data,0,data.length);
return clazz;
}
private byte[] getClassByteData(String name){
InputStream inputStream = null;
try {
String path = baseUrl+name.replaceAll("\\.", "/")+".class";
URL url = new URL(path);
byte[] buff = new byte[1024*4];
int len = -1;
inputStream = url.openStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((len=inputStream.read(buff))!=-1){
byteArrayOutputStream.write(buff,0,len);
}
return byteArrayOutputStream.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
if (inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
package my.classloader;
import java.lang.reflect.Method;
public class CustomClassLoaderTest {
public static void main(String[] args) {
String baseUrl = "file:///C:/Users/admin/Desktop/";
CustomClassLoader customClassLoader = new CustomClassLoader(baseUrl);
String className = "my.classloader.Test";
try {
Class clazz = customClassLoader.loadClass(className);
System.out.println(clazz.getClassLoader());
Object object = clazz.newInstance();
Method method = clazz.getMethod("hello");
method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}