顾名思义,类加载器(ClassLoader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
ClassLoader是负责加载类的对象。ClassLoader是一个抽象类。给定类的二进制名称,ClassLoader应尝试查找或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
每个Class类对象都包含一个Class.getClassLoader()引用,指向定义它的ClassLoader。
数组类的对象不是由ClassLoader创建的,而是根据Java运行时的要求自动创建的。由Class.getClassLoader()返回的数组类的ClassLoader与元素类型的ClassLoader相同;如果元素类型是基元类型,则数组类没有ClassLoader。
应用程序实现ClassLoader的子类,以扩展Java虚拟机动态加载类的方式。ClassLoader通常可由安全管理器用于指定安全域。
ClassLoader类使用委托模型来搜索类和资源。ClassLoader的每个实例都有一个相关的父ClassLoader。当请求查找类或资源时,ClassLoader实例将在试图查找类或资源本身之前,将对该类或资源的搜索委托给其父ClassLoader。虚拟机的内置ClassLoader称为“引导类加载器”,它本身没有父类,但可以充当ClassLoader实例的父类。
支持类的并发加载的ClassLoader称为支持并行的ClassLoader,需要在类初始化时通过调用{ClassLoader.registeraspallelcapable}方法注册它们自己。注意,默认情况下,ClassLoader类注册为支持并行。但是,如果其子类具有并行能力,则仍需要注册它们自己。在委托模型不是严格分层的环境中,ClassLoader需要具有并行能力,否则类装入可能导致死锁,因为装入器锁在类装入过程的持续时间内保持。
通常,Java虚拟机以依赖于平台的方式从本地文件系统加载类。例如,在UNIX系统上,虚拟机从由CLASSPATH环境变量定义的目录加载类。
但是,有些类可能不是源于文件;它们可能源于其他源,例如网络,或者可以由应用程序构造。方法{defineClass(String,byte[],int,int)}将字节数组转换为类的实例。这个新定义的类的实例可以使用{class.newInstance}创建。
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.lang.ClassLoader
类的方式实现自己的类加载器,以满足一些特殊的需求。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getParent()
方法可以得到。
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
if (classLoader != null){
System.out.println(classLoader.getParent().toString());
//输出:sun.misc.Launcher$ExtClassLoader@379619aa
}
}
ClassLoader中方法很多,但是与加载类相关的方法有下面这个几个:
getParent()
@CallerSensitive
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 权限检查
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
loadClass(String name)
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//返回引导类加载器加载的类;如果找不到,则返回null
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍然找不到,则调用findClass找到该类
long t1 = System.nanoTime();
//findClass需要重写
c = findClass(name);
//这是定义类装入器;记录统计信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//链接指定的 Java 类
resolveClass(c);
}
return c;
}
}
findClass(String name)
这个方法需要子类进行重写。
findLoadedClass(String name)
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
defineClass(String name, byte[] b, int off, int len)
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
//确定保护域
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
//定义类
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//设置证书
postDefineClass(c, protectionDomain);
return c;
}
开发自己的类加载器
public class CustomClassLoader extends ClassLoader {
private String rootDir;
public CustomClassLoader(String rootDir) {
this.rootDir = rootDir;
}
//重写findClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
//获取文件中的类内容
//获取文件中的类内容
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
类加载器与web容器
对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:
-
每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在
WEB-INF/classes
和WEB-INF/lib
目录下面。 -
多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
-
当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
更多精彩内容请关注微信公众号: