类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class
类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()
方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
java.lang.ClassLoader
类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class
类的一个实例。
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()
来获取它
Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。下面自己编写了个累加器FileSystemClassLoader来实现:代码如下:
package test.java;
public class LoaderClass {
@SuppressWarnings("unused")
private LoaderClass loader;
public void newInstance(Object loader) {
this.loader = (LoaderClass) loader;
}
}
package test.java;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Class findClass(String name) {
byte[] b = loadClassData(name);
if (b == null) {
try {
throw new ClassNotFoundException();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else {
return defineClass(name, b, 0, b.length);
}
return null;
}
private byte[] loadClassData(String className) {
File classFile = new File(this.addClassNameToPath(className));
try {
InputStream in = new FileInputStream(classFile);
ByteArrayOutputStream out = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] b = new byte[bufferSize];
int length = 0;
while ((length = in.read(b)) != -1) {
out.write(b, 0, length);
}
return out.toByteArray();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
protected String addClassNameToPath(String className) {
return this.rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
package test.java;
import java.lang.reflect.Method;
public class LoaderClassTest {
public static void main(String[] args) throws Exception {
String classPath = "D:\\workspace\\Test\\bin";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classPath);
String className = "test.java.LoaderClass";
Class<?> c1 = fscl1.findClass(className);
Object o1 = c1.newInstance();
Class<?> c2 = fscl2.findClass(className);
Object o2 = c2.newInstance();
Method newInstanceMethod = c1.getMethod("newInstance",
java.lang.Object.class);
newInstanceMethod.invoke(o1, o2);
}
}
运行结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at test.java.LoaderClassTest.main(LoaderClassTest.java:18)
Caused by: java.lang.ClassCastException: test.java.LoaderClass cannot be cast to test.java.LoaderClass
at test.java.LoaderClass.newInstance(LoaderClass.java:7)
... 5 more
可以看出出现了类转换异常!