Spring之JDK动态代理原理
前言
最近在学习Spring的源码和一些实现,也是学习到了JDK的动态代理这块,觉得JDK的动态代理还是有点东西的,所以写这篇文章记录一下。Spring源码还是有非常多真正深入的地方,并且非常有研究的必要。
一、什么是代理
代理
代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
静态代理
静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代理一个目标类,代理类和目标类都实现相同的接口。
动态代理
动态代理简单来说就是在程序执行过程中,创建代理对象,通过代理对象执行方法,给目标类的方法增加额外的功能,也叫做功能增强。
动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时再虚拟机中程序自动创建的。
二、如何实现JDK动态代理
因为JDK的静态代理比较简单,所以这里我们不再赘述,另一方面因为我是阅读Spring源码然后来学习JDK的动态代理实现的,所以我们就来模拟JDK动态代理的实现。
我们先写一个简单模拟jdk动态代理的测试代码,如下:
public class JdkProxyDemo {
interface Foo {
void foo();
}
// 被代理的对象,需要和代理对象实现同一个接口
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
运行结果:
proxy before...
target foo
proxy after...
jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系。
以上测试代码只是最简单的模拟JDK动态代理的实现,不能算是真正意义上的JDK动态代理,真正的动态代理是存在一个代理类,与被代理的对象实现同一个接口,并且实现被代理对象的所有方法,并且通过回调JDK中的InvocationHandler
来实现代理反射的。
代码如下:
被代理的对象,需要实现一个接口:
public class Main{
// 代理类和被代理类需要实现同一个接口
interface Foo {
void foo();
int bar();
}
// 被代理的类
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
public int bar() {
System.out.println("target bar");
return 100;
}
}
public static void main(String[] param) {
// 创建代理,这时传入 InvocationHandler
Foo proxy = new $Proxy0(new InvocationHandler() {
// 进入 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 功能增强
System.out.println("before...");
// 反射调用目标方法
return method.invoke(new Target(), args);
}
});
// 调用代理方法
proxy.foo();
proxy.bar();
}
}
手写的代理类,需要初始化代理类的静态方法和重写其中的实现,通过反射来进行代理。
// 继承Proxy可以使用jdk自带的InvocationHandler
// 与被代理类实现同一个接口Foo
public class $Proxy0 extends Proxy implements Foo {
// 构造方法,直接调用jdk的代理逻辑
public $Proxy0(InvocationHandler h) {
super(h);
}
// 进入代理方法
public void foo() {
try {
// 回调 InvocationHandler
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
// 捕获非检查性异常
throw e;
} catch (Throwable e) {
// 检查性异常需要转换成非检查性异常抛出
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
// 静态方法,直接初始化被代理对象的所有方法
static Method foo;
static Method bar;
static {
try {
foo = A12.Foo.class.getMethod("foo");
bar = A12.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}
1、方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
2、通过接口回调将【增强逻辑】置于代理类之外
3、配合接口方法反射(是多态调用),就可以再联动调用目标方法
4、会用 arthas 的 jad 工具反编译代理类
5、限制:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
三、JDK的InvocationHandler源码实现
public interface InvocationHandler {
// 处理代理实例上的方法调用并返回结果。
//当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
...
}
- proxy,代理后的实例对象
- method,代理类的方法
- args,代理方法的参数列表