暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

水煮Java虚拟机 - ClassLoader源码分析(二)

然笑 2021-12-07
611


在上一篇的 ClassLoader
源码分析文章中,已经对 ClassLoader
的部分方法进行了分析,具体可查看:水煮Java虚拟机 - ClassLoader源码分析(一),本文将继续分析余下的几个方法:
  • defineClass
  • resolveClass
  • getResource
  • definePackage


2.8 defineClass

#defineClass(String name, byte[] b, int off, int len)
方法负责将二进制的字节码转换为 java.lang.Class
对象,源码如下:
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}


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 对象
protectionDomain = preDefineClass(name, protectionDomain);
// 获取关联的位置
String source = defineClassSourceLocation(protectionDomain);
// 调用本地方法 defineClass1 来
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
// 设置类的签名者信息
postDefineClass(c, protectionDomain);
return c;
}


protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
int len = b.remaining();


// Use byte[] if not a direct ByteBufer:
// 判断是否使用的是直接内存
if (!b.isDirect()) {
// 判断是否为数组
if (b.hasArray()) {
return defineClass(name, b.array(),
b.position() + b.arrayOffset(), len,
protectionDomain);
} else {
// no array, or read-only array
byte[] tb = new byte[len];
b.get(tb); // get bytes out of byte buffer.
return defineClass(name, tb, 0, len, protectionDomain);
}
}
// 解析 ProtectionDomain 对象
protectionDomain = preDefineClass(name, protectionDomain);
// 获取类对应的位置
String source = defineClassSourceLocation(protectionDomain);
// 调用本地方法 defineClass2 定义类
Class<?> c = defineClass2(name, b, b.position(), len, protectionDomain, source);
// 设置类的签名者信息
postDefineClass(c, protectionDomain);
return c;
}


private native Class<?> defineClass0(String name, byte[] b, int off, int len,
ProtectionDomain pd);


private native Class<?> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);


private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
  • #preDefineClass(String name,ProtectionDomain pd)
    方法,用于确定类的保护域,其定义如下:
    private ProtectionDomain preDefineClass(String name,
    ProtectionDomain pd)
    {
    // 检查类名是否合法
    if (!checkName(name))
    throw new NoClassDefFoundError("IllegalName: " + name);


    // Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
    // relies on the fact that spoofing is impossible if a class has a name
    // of the form "java.*"
    // 判断类名是否为null或者是否以 java. 开头
    if ((name != null) && name.startsWith("java.")) {
    throw new SecurityException
    ("Prohibited package name: " +
    name.substring(0, name.lastIndexOf('.')));
    }
    if (pd == null) {
    pd = defaultDomain;
    }


    // 检查证书
    if (name != null) checkCerts(name, pd.getCodeSource());


    return pd;
    }


  • #defineClassSourceLocation(ProtectionDomain pd)
    方法用于获取类源文件的位置,如下:
    private String defineClassSourceLocation(ProtectionDomain pd)
    {
    // 获取 CodeSource 对象
    CodeSource cs = pd.getCodeSource();
    String source = null;
    if (cs != null && cs.getLocation() != null) {
    // 获取与此域关联的位置
    source = cs.getLocation().toString();
    }
    return source;
    }


  • #postDefineClass(Class<?> c, ProtectionDomain pd)
    方法,用于设置类的签名者信息,如下:
    private void postDefineClass(Class<?> c, ProtectionDomain pd)
    {
    if (pd.getCodeSource() != null) {
    // 获取证书集合
    Certificate certs[] = pd.getCodeSource().getCertificates();
    // 设置类的签名者
    if (certs != null)
    setSigners(c, certs);
    }
    }


2.9 resolveClass

#resolveClass(Class<?> c)
方法主要是用于完成指定 Class 对象的链接,其调用的是本地方法,如下:
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}


private native void resolveClass0(Class<?> c);


2.10 getResource

#getResource(String name)
方法,用于获取指定名称的资源,其实现如下:
public URL getResource(String name) {
URL url;
// 判断父类加载器是否为null
if (parent != null) {
// 从父类加载器中获取对应的资源
url = parent.getResource(name);
} else {
// 父类加载器为null,则从启动类加载器中获取对应的资源
url = getBootstrapResource(name);
}
// 若未获取到,则查找对应的资源
if (url == null) {
// findResource 返回null,需要子类重写
url = findResource(name);
}
return url;
}
  • #getBootstrapResource(String name)
    方法,用于从启动类加载器查找指定资源,实现如下:

    private static URL getBootstrapResource(String name) {
    // 获取启动类路径 URLClassPath 对象
    URLClassPath ucp = getBootstrapClassPath();
    // 创建 Resource 对象
    Resource res = ucp.getResource(name);
    // 获取资源对应的 URL
    return res != null ? res.getURL() : null;
    }


    static URLClassPath getBootstrapClassPath() {
    // 获取启动类路径
    return sun.misc.Launcher.getBootstrapClassPath();
    }


  • #findResource(String name)
    方法,返回null,需要子类重写,定义如下:
    protected URL findResource(String name) {
    return null;
    }



2.11 definePackage

#definePackage()
方法,用于按照包名定义包对象 java.lang.Package
,代码如下:
protected Package definePackage(String name, String specTitle,
String specVersion, String specVendor,
String implTitle, String implVersion,
String implVendor, URL sealBase)
throws IllegalArgumentException
{
// 同步 packages 对象,它为 HashMap 类型
synchronized (packages) {
// 从 packages 中获取指定的 Package 对象
Package pkg = getPackage(name);
if (pkg != null) {
// 若不存在,直接抛出异常
throw new IllegalArgumentException(name);
}
// 新创建 Package 对象
pkg = new Package(name, specTitle, specVersion, specVendor,
implTitle, implVersion, implVendor,
sealBase, this);
// 将其添加到 packages 中
packages.put(name, pkg);
return pkg;
}
}
  • #getPackage(String name)
    方法,用于从 packages 中获取指定的 Package 对象,其实现如下:
    protected Package getPackage(String name) {
    Package pkg;
    synchronized (packages) {
    // 获取 Package 对象
    pkg = packages.get(name);
    }
    // 若 pkg 为空
    if (pkg == null) {
    // 判断父类加载器是否为空
    if (parent != null) {
    // 父类加载器不为空,从父类加载器中获取 Package 对象
    pkg = parent.getPackage(name);
    } else {
    // 若父类加载器为空,则从已加载的系统包中获取指定的 Package 对象
    pkg = Package.getSystemPackage(name);
    }
    // 若 pkg 不为空
    if (pkg != null) {
    synchronized (packages) {
    // 再次从 packages 中获取指定的 Package 对象
    Package pkg2 = packages.get(name);
    if (pkg2 == null) {
    // 若仍然为空,则将 pkg 加入到 packages 中
    packages.put(name, pkg);
    } else {
    // 否则,将 pkg2 赋值给 pkg
    pkg = pkg2;
    }
    }
    }
    }
    return pkg;
    }
  • #getPackages()
    方法,用于获取当前 ClassLoader 和它的父类所定义的所有的 Package,其实现如下:
    protected Package[] getPackages() {
    Map<String, Package> map;
    // 对 packages 对象加锁,创建 map
    synchronized (packages) {
    map = new HashMap<>(packages);
    }
    Package[] pkgs;
    // 判断父类加载器是否为空,如果不为空,直接从父类获取
    if (parent != null) {
    pkgs = parent.getPackages();
    } else {
    // 否则获取系统的Packages
    pkgs = Package.getSystemPackages();
    }
    if (pkgs != null) {
    // 遍历获取到的 packages,添加至 map中
    for (int i = 0; i < pkgs.length; i++) {
    // 获取Package的名称,如果map中不存在,则添加至map中
    String pkgName = pkgs[i].getName();
    if (map.get(pkgName) == null) {
    map.put(pkgName, pkgs[i]);
    }
    }
    }

    // 将 Map 对象转换为 Package[] 数组
    return map.values().toArray(new Package[map.size()]);
    }

参考资料

【1】ClassLoader 源码分析  

【2】Java 安全模型介绍  

【3】分布式Java应用基础与实践  

【4】《jdk8u源码分析》sun.misc.Launcher


文章转载自然笑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论