一、四级类加载器简介
启动类加载器(Bootstrap):主要加载系统类
扩展类加载器(Extension),加载jre/lib/ext
应用类加载器(APP):加载ClassPath中的类
用户自定义加载器(Plugin),程序自定义
二、双亲委托机制
双亲委托机制,即当类被记载器加载时,首先会判断其是否已经被加载,若无则给父加载器加载,若再无则当前加载器加载。
目的时为了让核心类由启动类加载器加载核心类,不会被其他类加载器加载,确保上层核心类的正确性。
双亲委托机制的扩展:
目的:解决上层类加载器所加载的类无法访问下级类加载器加载的类的问题。
1.添加虚拟机参数 -Xbootclasspath/a:path path为类的所在地址,可将该地址的类的优先级变成bootstrap类加载器加载级别
2.使用ServiceLoader.load()方法来加载,来获取某接口的所有实现,转化变成接口类来使用
注意:模块系统的服务和这个不同,但是都能通过ServiceLoader进行加载。
三、自定义加载路径
弥补类搜素路径静态的不足
主要使用URLClassLoader,从多个URL(jar/文件目录)中加载类
1.加载文件目录
public static void test1() throws Exception
{
//URL支持http, https, file, jar 四种协议
URL url = new URL("file:E:/java-study/PMOOC11-03/PMOOC11-03-First/bin/");
//程序运行时,添加一个classpath路径
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> c = loader.loadClass("edu.ecnu.Hello");
//采用反射调用
Method m = c.getMethod("say");
m.invoke(c.newInstance());
System.out.println(c.getClassLoader());
System.out.println(c.getClassLoader().getParent());
}
2.加载jar包
public static void test2() throws Exception
{
URL url = new URL("file:E:/java-study/test1.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());
}
3.与线程结合
public static void test3() throws Exception
{
String project1 = "file:E:/java-study/PMOOC11-03/PMOOC11-03-First/bin/";
String project2 = "file:E:/java-study/PMOOC11-03/PMOOC11-03-Second/bin/";
new HelloThread(project1, Thread.currentThread().getContextClassLoader()).start();
new HelloThread(project2, Thread.currentThread().getContextClassLoader()).start();
}
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class HelloThread extends Thread{
private ClassLoader loader;
public HelloThread(String file, ClassLoader outerLoader)
{
try {
URL url = new URL(file);
this.loader = new URLClassLoader(new URL[]{url},outerLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
public void run()
{
Class<?> c;
try {
c = loader.loadClass("edu.ecnu.Hello");
//采用反射调用
Object obj = c.newInstance();
Method m = c.getMethod("say");
while(true)
{
m.invoke(obj);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.热加载,可随时替换java包
public static void test4() throws Exception{
String file = "file:E:/java-study/test1.jar";//可以热替换jar包从而改变输出
URL url = new URL(file);
URLClassLoader loader = new URLClassLoader(new URL[]{url});
int count = 0;
while(true){
Class<?> c = loader.loadClass("edu.ecnu.Hello");
Method m = c.getMethod("say");
m.invoke(c.newInstance());
Thread.sleep(1000);
if(++count%5==0){
System.out.println("reload");
loader.close();
loader = new URLClassLoader(new URL[]{url});
}
}
四、自定义加载器
继承ClassLoader类,
重写findClass(String className)方法
使用时默认先调用loadClass(className)来查看是否已经加载过了,然后委托双亲加载,若都没有,则再通过findClass加载。
- 在findClass中首先读取字节码文件
- 然后调用defineClass(className bytes,off,len)将类注册到虚拟机中
- 可以重写loadClass方法来突破双亲加载。
注意:不同类加载器加载的同一个类生成的对象,会作为两个类对待。不能转换,编译不会出错,但是运行会出错。
自定义加载器在加载的过程中还可以对类的字节码进行修改校验。
示例:
此处加密对Hello.class进行加密变成caesar
import java.io.*;
/**
* Encrypts a file using the Caesar cipher.
*
* @version 1.01 2012-06-10
* @author Cay Horstmann
*/
public class Caesar {
public static void main(String[] args) throws Exception {
int key = 3;
// 将Input类加密,输出到Output中
String input = "E:\\java-study\\PMOOC11-04\\bin\\edu\\ecnu\\Hello.class";
String output = "E:\\\\java-study\\PMOOC11-04\\bin\\edu\\ecnu\\Hello.caesar";
try (FileInputStream in = new FileInputStream(input); FileOutputStream out = new FileOutputStream(output)) {
int ch;
while ((ch = in.read()) != -1) {
// 每个字节码+key
byte c = (byte) (ch + key);
out.write(c);
}
}
System.out.println("Caesar done");
}
}
2.利用自定义加载器进行加载
import java.lang.reflect.*;
public class ClassLoaderTest
{
public static void main(String[] args)
{
try
{
ClassLoader loader = new CryptoClassLoader();
//loadClass去加载Hello类
//loadClass是ClassLoader默认方法,通过委托双亲去加载类
//如加载不到,则调用findClass方法加载
Class<?> c = loader.loadClass("edu.ecnu.Hello");
Method m = c.getMethod("say");
m.invoke(c.newInstance());
System.out.println(c.getClassLoader().getClass().getName());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
3.此处为自定义的加载器
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
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;
}
}
参考:中国大学mooc《Java核心技术》