jdk 动态代理
模拟 jdk 动态代理
public class A10 {
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) {
// ⬇️1. 创建代理,这时传入 InvocationHandler
Foo proxy = new $Proxy0(new InvocationHandler() {
// ⬇️5. 进入 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// ⬇️6. 功能增强
System.out.println("before...");
// ⬇️7. 反射调用目标方法
return method.invoke(new Target(), args);
}
});
// ⬇️2. 调用代理方法
proxy.foo();
proxy.bar();
}
}
模拟代理实现
// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
// ⬇️3. 进入代理方法
public void foo() {
try {
// ⬇️4. 回调 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());
}
}
}
总结:
-
方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
-
通过接口回调将【增强逻辑】置于代理类之外
-
配合接口方法反射(是多态调用),就可以再联动调用目标方法
-
会用 arthas 的 jad 工具反编译代理类
-
限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
jdk动态代理优化
前 16 次反射性能较低
第 17 次调用会生成代理类,优化为非反射调用
会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类
cglib 代理
public class A10Application {
public static void main(String[] args) throws InvocationTargetException {
Target target = new Target();
Proxy proxy = new Proxy();
proxy.setCallbacks(new Callback[]{(MethodInterceptor) (p, m, a, mp) -> {
System.out.println("proxy before..." + mp.getSignature());
// ⬇️调用目标方法(三种)
// Object result = m.invoke(target, a); // ⬅️反射调用
// Object result = mp.invoke(target, a); // ⬅️非反射调用, 结合目标用
Object result = mp.invokeSuper(p, a); // ⬅️非反射调用, 结合代理用
System.out.println("proxy after..." + mp.getSignature());
return result;
}});
// ⬇️调用代理方法
proxy.save();
}
}
总结:
和 jdk 动态代理原理差不多
回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
-
method.invoke 是反射调用,必须调用到足够次数才会进行优化
-
methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
-
methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
cglib 避免反射调用
当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类
-
ProxyFastClass 配合代理对象一起使用, 避免反射
-
TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
TargetFastClass 记录了 Target 中方法与编号的对应关系
-
save(long) 编号 2
-
save(int) 编号 1
-
save() 编号 0
-
首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号
-
然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射
ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法
-
saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)
-
saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)
-
saveSuper() 编号 0,不增强, 仅是调用 super.save()
-
查找方式与 TargetFastClass 类似
public class ProxyFastClass {
static Signature s0 = new Signature("saveSuper", "()V");
static Signature s1 = new Signature("saveSuper", "(I)V");
static Signature s2 = new Signature("saveSuper", "(J)V");
// 获取代理方法的编号
/*
Proxy
saveSuper() 0
saveSuper(int) 1
saveSuper(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}
// 根据方法编号, 正常调用目标对象方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
((Proxy) proxy).saveSuper();
return null;
} else if (index == 1) {
((Proxy) proxy).saveSuper((int) args[0]);
return null;
} else if (index == 2) {
((Proxy) proxy).saveSuper((long) args[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
ProxyFastClass fastClass = new ProxyFastClass();
int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
System.out.println(index);
fastClass.invoke(index, new Proxy(), new Object[0]);
}
}
为什么有这么麻烦的一套东西呢?
-
避免反射, 提高性能, 代价是一个代理类配两个 FastClass(两个代理) 类, 代理类中还得增加仅调用 super 的一堆方法
-
用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
public class TargetFastClass {
static Signature s0 = new Signature("save", "()V");
static Signature s1 = new Signature("save", "(I)V");
static Signature s2 = new Signature("save", "(J)V");
// 获取目标方法的编号
/*
Target
save() 0
save(int) 1
save(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}
// 根据方法编号, 正常调用目标对象方法
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((Target) target).save();
return null;
} else if (index == 1) {
((Target) target).save((int) args[0]);
return null;
} else if (index == 2) {
((Target) target).save((long) args[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
TargetFastClass fastClass = new TargetFastClass();
int index = fastClass.getIndex(new Signature("save", "(I)V"));
System.out.println(index);
fastClass.invoke(index, new Target(), new Object[]{100});
}
}