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; | |
} | |
} |