AOP
11) AOP 实现之 proxy
演示1 - jdk 动态代理
public class JdkProxyDemo { interface Foo{ void foo(); } static class Targer implements Foo{ public void foo(){ System.out.println("foo"); } } //使用jdk自带的动态代理增强 //jdk 动态代理要求目标**必须**实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系 public static void main(String[] args) { Targer t = new Targer(); ClassLoader classLoader = JDKproxyDemo.class.getClassLoader(); Foo o =(Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object object = method.invoke(t, args); System.out.println("after"); return object; } }); o.foo(); } }
运行结果
proxy before... target foo proxy after...
收获💡
-
jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
演示2 - cglib 代理
public class CglibProxyDemo { static public class Target{ public void foo(){ System.out.println("foo"); } } //* cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系 //* 限制⛔:根据上述分析 final 类无法被 cglib 增强 public static void main(String[] args) { Target t = new Target(); Target proxy =(Target) Enhancer.create(Target.class, new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //代理类 执行的方法 参数 方法对象 System.out.println("before"); methodProxy.invoke(t,args);//底层没使用反射 //spring底层使用的 method.invoke(t,args);//最原始的反射是实现 methodProxy.invokeSuper(o,args);//只需要代理对象 return null; } }); proxy.foo(); } }
运行结果与 jdk 动态代理相同
收获💡
-
cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
-
限制⛔:根据上述分析 final 类无法被 cglib 增强
12) jdk 动态代理进阶
演示1 - 模拟 jdk 动态代理
public class A12 { 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(); } }
模拟代理实现
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; // ⬇️这就是 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 方法均不能通过代理实现
演示2 - 方法反射优化
代码参考
// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED public class TestMethodInvoke { public static void main(String[] args) throws Exception { Method foo = TestMethodInvoke.class.getMethod("foo", int.class); for (int i = 1; i <= 17; i++) { show(i, foo); foo.invoke(null, i); } System.in.read(); } // 方法反射调用时, 底层 MethodAccessor 的实现类 private static void show(int i, Method foo) throws Exception { Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor"); getMethodAccessor.setAccessible(true); Object invoke = getMethodAccessor.invoke(foo); if (invoke == null) { System.out.println(i + ":" + null); return; } Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate"); delegate.setAccessible(true); System.out.println(i + ":" + delegate.get(invoke)); } public static void foo(int i) { System.out.println(i + ":" + "foo"); } }
收获💡
-
前 16 次反射性能较低
-
第 17 次调用会生成代理类,优化为非反射调用
-
会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类
注意
运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
13) cglib 代理进阶
演示 - 模拟 cglib 代理
代码参考
被代理类:
public class Target { public void save() { System.out.println("save()"); } public void save(int i) { System.out.println("save(int)"); } public void save(long j) { System.out.println("save(long)"); } }
代理类:
public class Proxy extends Target { private MethodInterceptor methodInterceptor; public void setMethodInterceptor(MethodInterceptor methodInterceptor) { this.methodInterceptor = methodInterceptor; } static Method save0; static Method save1; static Method save2; static MethodProxy save0Proxy; static MethodProxy save1Proxy; static MethodProxy save2Proxy; static { try { save0 = Target.class.getMethod("save"); save1 = Target.class.getMethod("save", int.class); save2 = Target.class.getMethod("save", long.class); save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper"); save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper"); save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法 public void saveSuper() { super.save(); } public void saveSuper(int i) { super.save(i); } public void saveSuper(long j) { super.save(j); } // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法 @Override public void save() { try { methodInterceptor.intercept(this, save0, new Object[0], save0Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(int i) { try { methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(long j) { try { methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
收获💡
和 jdk 动态代理原理查不多
-
回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
-
调用目标时有所改进,见下面代码片段
-
method.invoke 是反射调用,必须调用到足够次数才会进行优化
-
methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
-
methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
-
public class A14Application { 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(); } }
注意
调用 Object 的方法, 后两种在 jdk >= 9 时都有问题, 需要 --add-opens java.base/java.lang=ALL-UNNAMED
14) cglib 避免反射调用
演示 - cglib 如何避免反射
代码参考
代理的fastclass优化
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]); } }
target代理的fastclass优化:
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}); } }
收获💡
-
当调用 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 类似
-
-
为什么有这么麻烦的一套东西呢?
-
避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
-
用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
-