Java基础知识整理7:ClassLoader

时间:2022-06-13 19:39:42

JDK描述

public abstract class ClassLoader

extends Object

类加载器是负责加载类的对象。ClassLoader类是一个抽象类。如果给定类的Binary Name,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”,返回二进制流。

每个 Class 对象都包含一个对定义它的ClassLoader的引用。

数组类的 Class 对象不是由类加载器创建的,而是由Java运行时根据需要自动创建。数组类的类加载器由 Class.getClassLoader()返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。

应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。

类加载器通常由安全管理器使用,用于指示安全域。

ClassLoader 类使用委托模型来搜索类和资源。每个ClassLoader实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。虚拟机的内置类加载器(称为 "bootstrap class loader")本身没有父类加载器,但是可以将它用作ClassLoader实例的父类加载器。

通常情况下,Java 虚拟机以与平台有关的方式,从本地文件系统中加载类。例如,在 UNIX 系统中,虚拟机从 CLASSPATH 环境变量定义的目录中加载类。

然而,有些类可能并非源自一个文件;它们可能源自其他来源(如网络),也可能是由应用程序构造的。defineClass方法将一个byte数组转换为Class类的实例。这种新定义的类的实例可以使用 Class.newInstance 来创建。

类加载器所创建对象的方法和构造方法可以引用其他类。为了确定引用的类,Java 虚拟机将调用最初创建该类的类加载器的loadClass方法。

例如,应用程序可以创建一个网络类加载器,从服务器中下载类文件。示例代码如下所示:

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
......

网络类加载器子类必须定义方法findClass和loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。示例实现如下:

     class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }

Binary Name 二进制名称

按照《Java Language Specification》的定义,任何作为 String 类型参数传递给 ClassLoader 中方法的类名称都必须是一个二进制名称。

有效类名称的示例包括:

"java.lang.String" 
"javax.swing.JSpinner$DefaultEditor"
    JSpinner的内部类DefaultEditor
"java.security.KeyStore$Builder$FileBuilder$1"
    KeyStore的内部类Builder的内部类FileBuilder
"java.net.URLClassLoader$3$1"
    KeyStore的内部类URLClassLoader的第三个匿名内部类的第一个匿名内部类

代码分析

String的变量

  • private boolean initialized = false; 如果成功初始化并通过安全检查,这个变量会设置为true;、
  • private ClassLoader parent; 保存父加载器的引用
  • private Hashtable package2certs = new Hashtable(11); 哈希表,证书?
  • java.security.cert.Certificate[] nocerts; 共享所有包中未签名的类
  • private Vector classes = new Vector(); 保存所有被加载的类,JVM会调用addClass(Class c)方法加入
  • private Set domains = new HashSet(); 本加载器加载的类的初始被保护的domains
  • private HashMap packages = new HashMap(); 保存包名到Package类的映射
  • private static ClassLoader scl; 系统类加载器
  • private static boolean sclSet; 设置系统类加载器后该值为true

字符数组和offset及count都是final的,必须在构造方法中初始化,之后不能修改。

构造方法

  • protected ClassLoader(ClassLoader parent) 构造方法,parent是指导的父类加载器
  • protected ClassLoader()
protected ClassLoader(ClassLoader parent) {
    SecurityManager security = System.getSecurityManager();//获取安全管理器
    if (security != null) {
        security.checkCreateClassLoader();
    }
    this.parent = parent;
    initialized = true;
}
protected ClassLoader() {
    SecurityManager security = System.getSecurityManager();//获取安全管理器
    if (security != null) {
        security.checkCreateClassLoader();//检查SecurityConstants.CREATE_CLASSLOADER_PERMISSION
    }
    this.parent = getSystemClassLoader();//不指定父类加载时,默认为scl,系统类加载器  AppClasssLoader
    initialized = true;
}

其他方法

加载类

  • public Class<?> loadClass(String name) 加载类,name为binary name
  • protected synchronized Class<?> loadClass(String name, boolean resolve) 加载类,name为binary name,resolve为是否需要解析【加载-连接(验证-准备-解析)-初始化】
  • loadClassInternal JVM会调用这个类来加载Class,内部调用了loadClass()方法
 protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name); //掉用本地方法findLoadedClass0查找是否已经加载
    if (c == null) {
        try {
        if (parent != null) {//如果父加载器不为空,则调用父加载器的loadClass方法
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClass0(name);//若父加载器为空,调用根加载器加载
        }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name); //如果父加载器都没找到则走到了这里,调用本类的findClass查找Class类
        }
    }
    if (resolve) {
        resolveClass(c); //调用本地方法resolveClass0解析
    }
    return c;
    }
  • protected Class<?> findClass(String name) 查找类,给定binary name返回Class类,一般会先读取一个字节数组,再调用defineClass
  • protected final Class<?> defineClass(byte[] b, int off, int len) 将字节数组转为Class类的实例。在Class被使用执行必须进行解析,这个方法不推荐。
  • protected final Class<?> defineClass(String name, byte[] b, int off, int len)
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                     ProtectionDomain protectionDomain)
    throws ClassFormatError
    {
    check(); //检查initialized变量,构造函数成功会设置为true
    protectionDomain = preDefineClass(name, protectionDomain);

    Class c = null;
        String source = defineClassSourceLocation(protectionDomain);

    try {
        c = defineClass1(name, b, off, len, protectionDomain, source); //native方法
    } catch (ClassFormatError cfe) {
        c = defineTransformedClass(name, b, off, len, protectionDomain, cfe, source);
    }

    postDefineClass(c, protectionDomain);
    return c;
    }
    
//[checkName],检查name是否为一个有效的binary name,
 private boolean checkName(String name) {
    if ((name == null) || (name.length() == 0))
        return true;
    //若包含/或者 VM不允许数组语法时以[开头
    if ((name.indexOf('/') != -1)
        || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
        return false;
    return true;
    }

URLClassLoader

private final URLClassPath ucp;
protected Class<?> findClass(final String name)
         throws ClassNotFoundException
    {
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class>() {
                    public Class run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            throw new ClassNotFoundException(name);
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }

相关问题

ClassLoader实际使用场景

热部署,如tomcat开启热部署后,class file被修改时会重新加载。
代码保护
加解密

注意问题

自定义加载器加载的类不能适java/javax和其他子包的类
相同的Class=相同的ClassName+ PackageName + ClassLoader

总结