前面几篇文章或多或少涉及到了代理模式的方方面面,从静态代理到动态代理的介绍使用,到mybaties对代理模式的实际运用,我们会想到代理模式再深一层是什么?我们只会用jdkproxy,但是在java语言的视角中,代理模式又是怎么运行的? 这个系列文章将从代理模式再出发!探讨跟代理相关的classloader,jvm字节码等概念,最终的目标是实现一个自己的代理框架。这些文章不是面面俱到的大谈jvm,classloader等,只是一个总结,给的是一个研究的方向,自己也是刚刚入jvm的小白,多多总结,多多学习。
一.代理模式的原理
在我们写java代码的时候,ide帮我们将java文件编译成class文件,然后class文件会交给jvm虚拟机去运行,把java文件编译成class文件这一过程中就有许多学问。我们可以在提交给jvm虚拟机运行前修改class文件(通过asm框架等方法),这个灵活的特性可以帮我们实现很多功能,比如我们的代理模式也是因为插手了这一步,修改了class文件(对class文件动了手术),然后虚拟机接收到的文件已经是修改后的class文件,从而实现动态增强类功能,达到代理的目的。当然,在jdkproxy中,不是直接对被代理类动手术,而是创建了一个新类,这个新类实现了被代理类的接口,然后通过InvocationHandler对象实现动态代理功能。这样做既对被代理类达到无损的效果,又实现了功能。
二.ClassLoader初探
前面说到“java文件->class文件->class文件提交虚拟机”,ClassLoader 在这个过程中扮演者很重要的角色,它使得 Java 类可以被动态加载到 Java 虚拟机中并执行,java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。在java系统中,ClassLoader不是只有一个,你可以自定义ClassLoader,只需要继承ClassLoader即可,为什么java设计允许多个ClassLoader呢?比如之前代理的需求,在你需要对某个class文件特殊处理时,你渴望拥有自己的ClassLoader,在一些代码加密,热部署之类的领域,可以写自己特定需求的ClassLoader就显得极为方便。后面我们会写自己的ClassLoader,先理解完概念。
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java
虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 - 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java
类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。
在上图中,可以看出java系统也有好几层ClassLoader,它们似乎有联系,这就引出了“双亲委派模型”
双亲委派模型
每个ClassLoader有自己的父类,这个父类不是继承概念上的父类,而是类似于责任链模式的关系,每一个ClassLoader有自己的上级(ext 和 bootstrap classloader 除外,它是最顶级),双亲委派的工作原理就是 当一个类加载器收到类加载请求,它会先看看自己是否以前加载过该类,如果有,会直接返回上次加载过的,如果没有,则会给自己的上级类加载器加载,一级一级往上,直到父加载器说我找不到这个类,也就是加载不到这个类,这个类就会交给子加载器加载。
使用双亲委派模型有个很好的好处就是 不会不同的类加载器都加载同一个类,不然会造成程序混乱,因为对一个对象是否一样,除了它的类名和包名是否相等,还要考虑是否是同一个类加载器加载的。
刚刚都是通过概念了解classloader,现在通过代码示例加深对classloader的直观印象。
三.自己写一个ClassLoader
在写之前,我们先看一下classloader的入口方法,loadClass方法,如果你没有复写这个方法,默认就是双亲委派模型的形式,看源码:
synchronized (getClassLoadingLock(name)) {
// 首先看看这个类是不是加载过,这里有个缓存机制
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果有父加载器,就先调用父加载器的loadClass方法
c = parent.loadClass(name, false);
} else {
// 如果该类加载器已经没有父加载器,就直接找Bootstrap classloader,Bootstrap ClassLoader是用C++编写的,在Java中看不到它
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//如果父加载器没法找到该类,那就只能自己来加载了,调用findClass方法查找。
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
整个过程就是双亲委派的代码实现,很简单,但是这个实现却保证了在java系统里不会出现一个类被同时两个类加载器加载的情况。
然后我们开始写一个简单的classloader:
public class SimpleClassLoader extends ClassLoader {
//加载class类的入口靠的是这个方法,在双亲委派模型中,如果父加载器的findClass方法找不到类时,最后就会执行本方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// bootstrap ext app 三个类加载器都找不到,最后还要我自己来找,那我也找不到,我就随意直接返回Test类吧
InputStream map = Test.class.getResourceAsStream("Test.class");
try {
byte[] bytes = new byte[map.available()];
map.read(bytes);
return super.defineClass(Test.class.getName(), bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// 如果你的classloader不想破坏双亲委派模型,就不要自己复写
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return super.loadClass(name);
}
}
测试类 Test:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
SimpleClassLoader simpleClassLoader = new SimpleClassLoader();
Class<?> aClass = simpleClassLoader.loadClass("JDK.Array.Man1");
System.out.println(aClass);
}
}
simpleClassLoader 用空的构造器会默认关联 appclassloader ,这是默认 该线程关联的classloader,每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类,使用
Thread.currentThread().getContextClassLoader() ;
可以知道当前是appclassloader作为线程上下文类加载器。
然后我们调用入口方法 loadClass ,里面写一个不存在的类名,这样根据双亲委派的规则,父加载器都不会加载到这个类,最后必须要用我们的findClass方法解决这个问题,再看一下我们复写的findClass方法,我们直接用Test测试类作为加载的类,最后有一个defineClass方法,把字节数组 中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。所以这个方法是加载器的核心方法,且不能继承复写。
最后我们可以发现加载的类是由simpleClassLoader加载
如果我们写的类名是真实存在的,换句话说 appclassloader能够加载到,那我们的simpleClassLoader就只是加载类的发起者,而真正的执行者却是appclassloader
simpleClassLoader.loadClass("JDK.classLoader.Test");
三.总结
我们在后面 自己实现动态代理的时候 需要用到calssloader,所以这篇文章只是大致的介绍,网上关于calssloader的文章也是数不胜数,感谢网上的一些资料,这里做一个学习的总结。