MOOC 个人学习笔记

# 1. 类加载机制

# 类加载器 ClassLoader

  • 负责查找、加载、校验字节码的应用程序
  • java.lang.ClassLoader
    • load (String className) 根据名字加载一个类,返回类的实例
    • defineClass (String name, byte [] b, int off, int len) 将一个字节流定义一个类
    • findClass (String name) 查找一个类
    • findLoadedClass (String name) 在已加载的类中,查找一个类
    • 成员变量 ClassLoader parent

# JVM 四级类加载器

  • 启动类加载器 (Bootstrap),系统类 rt.jar
  • 扩展类加载器 (Extension),jre/lib/ext
  • 应用类加载器 (App),classpath
  • 用户自定义加载器 (Plugin),程序自定义

# 类加载器双亲委托

  • 首先判断是否已经加载
  • 若无,找父加载器加载
  • 若再无,由当前加载器加载

# 2. 双亲委托加载扩展

  • Java 严格执行双亲委托机制
    • 类会由最顶层的加载器来加载,如没有,才由下级加载器加载
    • 委托是单向的,确保上层核心的类的正确性
    • 但是上级类加载器所加载的类,无法访问下级类加载器所加载的类
      • 例如,java.lang.String 无法访问自定义的一个 Test 类
      • Java 是一个遵循契约设计的程序语言,核心类库提供接口,应用层提供实现
      • 核心类库是 BootstrapClassLoader 加载
      • 应用层是 AppClassLoader 加载
      • 典型例子是 JDBC 和 XML Parser 等

# 双亲委托的补充

  • 执行 Java,添加虚拟机参数 - Xbootclasspath/a:path,将类路径配置为 Bootstrap 等级
  • 使用 ServiceLoader.load 方法,来加载 (底层加载器所加载的类)

# ServiceLoader

  • JDK 6 引入的一种新特性,是用于加载服务的一种工具
  • 服务有接口定义和具体的实现类 (服务提供者)
  • SPI 机制,Service Provider Interface
  • 一个服务提供者会在 jar 包中有 META-INF/services 目录,里面放一个文件,名字同接口名字。内容的每一行都是接口的一个实现类
  • load 方法,可以用当前线程的类加载器来获取某接口的所有实现,当然也都是转为接口类来使用
  • 注意:此服务和 Java 9 模块系统的服务略有差别,但是都能通过 ServiceLoader 进行加载

# 3. 自定义类加载路径

# 自定义类加载器

  • 自定义加载路径
    • 弥补类搜索路径静态的不足
    • URLClassLoader, 从多个 URL (jar 或者目录) 中加载类
  • 自定义类加载器
    • 继承 ClassLoader 类
    • 重写 findClass (String className) 方法

# 自定义加载路径

  • 弥补类搜索路径静态的不足
  • 前 3 个加载的路径都是运行前确定的
  • Java 提供 URLClassLoader
    • 程序运行时修改类的加载路径
    • 可从多个来源中加载类

# URLClassLoader

  • 继承于 ClassLoader
  • 程序运行时增加新的类加载路径
  • 可从多个来源中加载类
    • 目录
    • jar 包
    • 网络
  • addURL 添加路径
  • close 方法关闭
//URL 支持 http, https, file, jar 四种协议
URL url = new URL("file:E:/java/source/PMOOC11-03-First/bin/");
//URL url = new URL("file:C:/Users/Tom/Desktop/PMOOC11-03-First.jar");
// 程序运行时,添加一个 classpath 路径
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> c = loader.loadClass("edu.ecnu.Hello");
// 采用反射调用
Method m = c.getMethod("say");
m.invoke(c.newInstance());

# 4. 自定义类加载器

  • 继承 ClassLoader 类
  • 重写 findClass (String className) 方法
  • 使用时,默认先调用 loadClass (className) 来查看是否已经加载过,然后委托双亲加载,如果都没有,再通过 findClass 加载返回
    • 在 findClass 中,首先读取字节码文件
    • 然后,调用 defineClass (className, bytes, off, len) 将类注册到虚拟机中
    • 可以重写 loadClass 方法来突破双亲加载
  • 同一个类可以被不同层级的加载器加载,且作为 2 个类对待
class CryptoClassLoader extends ClassLoader 
{
   private int key = 3; 
   
   public Class<?> findClass(String name) throws ClassNotFoundException
   {
      try
      {
         byte[] classBytes = null;
         // 读取 Hello.caesar 文件,得到所有字节流
         classBytes = loadClassBytes(name);
         // 调用 defineClass 方法产生一个类,并在 VM 中注册
         Class<?> cl = defineClass(name, classBytes, 0, classBytes.length);
         if (cl == null) throw new ClassNotFoundException(name);
         return cl;
      }
      catch (IOException e)
      {
         throw new ClassNotFoundException(name);
      }
   }
   /**
    * Loads and decrypt the class file bytes.
    * @param name the class name
    * @return an array with the class file bytes
    */
   private byte[] loadClassBytes(String name) throws IOException
   {
      String cname = "E:/java/source/PMOOC11-04/bin/edu/ecnu/Hello.caesar";
      byte[] bytes = Files.readAllBytes(Paths.get(cname));
      for (int i = 0; i < bytes.length; i++)
         bytes[i] = (byte) (bytes[i] - key);
      return bytes;
   }
}