Java中的動態(tài)代理原理及實現(xiàn)
前言
動態(tài)是相對于靜態(tài)而言,何為靜態(tài),即編碼時手動編寫代理類、委托類。而動態(tài)呢,是不編寫具體實現(xiàn)類,等到使用時,動態(tài)創(chuàng)建一個來實現(xiàn)代理的目的。
為什么有了靜態(tài)代理還需要動態(tài)代理呢?靜態(tài)代理畢竟是你手動編碼的,如果需要對很多個方法進行一些公共處理(比如耗時,日志等),你需要在每個方法處修改代碼,而且邏輯上都是相通的,為什么不能抽取出來呢。如果使用動態(tài)代理的話,你只需要指定規(guī)則,那么動態(tài)代理就可以根據(jù)你指定的規(guī)則進行處理。
本文主要研究動態(tài)代理的兩種實現(xiàn)方式:JDK動態(tài)代理和CGLib動態(tài)代理
一、JDK動態(tài)代理
JDK動態(tài)代理的核心是JDK提供的Proxy類和Invocation接口,基于接口。
看個例子
1、公共接口
public interface Hello { void sayHi(String name); }
2、委托類
@Slf4j public class HelloImpl implements Hello { @Override public void sayHi(String name) { log.info("hello, {}", name); } }
3、實現(xiàn)InvocationHandler接口
@Slf4j public class HelloInvocationHandler<T> implements InvocationHandler { private T target; public HelloInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("執(zhí)行了{}方法", method.getName()); method.invoke(target, args); return null; } }
為什么實現(xiàn)這個接口呢,主要是實現(xiàn)其invoke方法,該方法有三個參數(shù)
proxy——動態(tài)代理實例
method——被調(diào)用的方法
args——方法入?yún)ⅲ绻菬o參方法,則為null
在invoke里,我們就可以對方法進行一些特殊處理,這里只做了一個簡單的演示,在執(zhí)行委托類的方法之前,打印一行日志。實際可以在方法前、方法后、方法異常等等場景進行想要的處理。
4、創(chuàng)建代理類
@Slf4j public class ProxyTest { public static void main(String[] args) { Hello hello = new HelloImpl(); InvocationHandler handler = new HelloInvocationHandler<>(hello); Hello helloProxy = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class<?>[]{Hello.class}, handler); helloProxy.sayHi("proxy"); } } //輸出 執(zhí)行了sayHi方法 hello, proxy
根據(jù)3創(chuàng)建一個調(diào)用處理器handler,通過Proxy的newProxyInstance方法生成代理類的實例。
入?yún)⒎謩e為:classLoader,要代理的接口列表,調(diào)用分發(fā)器handler。
通過該代理實例調(diào)用方法,將會回調(diào)hanlder中的invoke,從而達到代理的目的。
上述例子是直接調(diào)用newProxyInstance來生成代理實例,還有一種方法是先生成代理類,然后再構造代理實例。
@Slf4j public class ProxyTest { public static void main(String[] args) throws Exception { Hello hello = new HelloImpl(); InvocationHandler handler = new HelloInvocationHandler<>(hello); // Hello helloProxy = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class<?>[]{Hello.class}, handler); // helloProxy.sayHi("proxy"); Class<?> proxyClass = Proxy.getProxyClass(Hello.class.getClassLoader(), Hello.class); log.info("name:{}", proxyClass.getName()); Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); Hello helloInstance = (Hello) constructor.newInstance(handler); helloInstance.sayHi("proxy"); } }
//輸出
name:com.sun.proxy.$Proxy0
執(zhí)行了sayHi方法
hello, proxy
生成的代理類名稱為$Proxy0,0為Proxy遞增生成的編號,如果有多個代理類,則名稱從$Proxy1依次類推。將生成的代理類proxyClass保存下來(默認保存到內(nèi)存中,并不會保存成文件,此處只是為了研究),命名為Hello$Proxy0.class,打開看下
public final class Hello$Proxy0 extends Proxy implements Hello { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public Hello$Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void sayHi(String var1) throws { try { super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.brain.demo.aop.Hello").getMethod("sayHi", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
代理類繼承Proxy,實現(xiàn)了Hello接口。static代碼塊中,通過反射拿到接口中的方法(本例中為Hello中的sayHi方法),Object中的equals、toString、hashCode方法。
對這些方法分別進行代理,具體表現(xiàn)為通過代理實例調(diào)用方法時,回調(diào)InvocationHandler實例中的invoke方法,即3中所述。
至此,JDK動態(tài)代理的流程就清楚了。Proxy生成代理類,持有InvocationHandler實例,而InvocationHandler實例又持有委托類實例。通過代理類實例調(diào)用接口方法,由InvocationHandler實例攔截,進行相應處理后再調(diào)用真正的實現(xiàn)方法。
二、CGLib動態(tài)代理
CGLib基于繼承,通過繼承代理類覆蓋其中的方法來實現(xiàn)代理的功能。
1、委托類
@Slf4j public class Teacher { public void sayHi() { log.info("大家好"); } }
2、方法攔截器
@Slf4j public class CglibMethodInterceptor implements MethodInterceptor { public Object CglibProxyGeneratory(Class target) { // 創(chuàng)建加強器,用來創(chuàng)建動態(tài)代理類 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { log.info("調(diào)用了方法:{}", method.getName()); methodProxy.invokeSuper(o, objects); return null; } }
3、生成代理類實例
Teacher teacher = (Teacher) new CglibMethodInterceptor().CglibProxyGeneratory(Teacher.class); teacher.sayHi();
//輸出
調(diào)用了方法:sayHi
大家好
將生成的委托類保存下來,發(fā)現(xiàn)會有三個class文件生成。
Teacher$$FastClassByCGLIB$$4e4ecf50(委托類fastclass) Teacher$$EnhancerByCGLIB$$332f7724(代理類) Teacher$$EnhancerByCGLIB$$332f7724$$FastClassByCGLIB$$3b18b46c(代理類fastclass)
看下生成的代理類
public class Teacher$$EnhancerByCGLIB$$332f7724 extends Teacher implements Factory { private boolean CGLIB$BOUND; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static final Method CGLIB$sayHi$0$Method; private static final MethodProxy CGLIB$sayHi$0$Proxy; private static final Object[] CGLIB$emptyArgs; private static final Method CGLIB$finalize$1$Method; private static final MethodProxy CGLIB$finalize$1$Proxy; private static final Method CGLIB$equals$2$Method; private static final MethodProxy CGLIB$equals$2$Proxy; private static final Method CGLIB$toString$3$Method; private static final MethodProxy CGLIB$toString$3$Proxy; private static final Method CGLIB$hashCode$4$Method; private static final MethodProxy CGLIB$hashCode$4$Proxy; private static final Method CGLIB$clone$5$Method; private static final MethodProxy CGLIB$clone$5$Proxy; static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.brain.demo.aop.Teacher$$EnhancerByCGLIB$$332f7724"); Class var1; Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$finalize$1$Method = var10000[0]; CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1"); CGLIB$equals$2$Method = var10000[1]; CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2"); CGLIB$toString$3$Method = var10000[2]; CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3"); CGLIB$hashCode$4$Method = var10000[3]; CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4"); CGLIB$clone$5$Method = var10000[4]; CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5"); CGLIB$sayHi$0$Method = ReflectUtils.findMethods(new String[]{"sayHi", "()V"}, (var1 = Class.forName("com.brain.demo.aop.Teacher")).getDeclaredMethods())[0]; CGLIB$sayHi$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHi", "CGLIB$sayHi$0"); } final void CGLIB$sayHi$0() { super.sayHi(); } public final void sayHi() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$sayHi$0$Method, CGLIB$emptyArgs, CGLIB$sayHi$0$Proxy); } else { super.sayHi(); } } public static MethodProxy CGLIB$findMethodProxy(Signature var0) { String var10000 = var0.toString(); switch(var10000.hashCode()) { case -2012941911: if (var10000.equals("sayHi()V")) { return CGLIB$sayHi$0$Proxy; } break; case -1574182249: if (var10000.equals("finalize()V")) { return CGLIB$finalize$1$Proxy; } break; case -508378822: if (var10000.equals("clone()Ljava/lang/Object;")) { return CGLIB$clone$5$Proxy; } break; case 1826985398: if (var10000.equals("equals(Ljava/lang/Object;)Z")) { return CGLIB$equals$2$Proxy; } break; case 1913648695: if (var10000.equals("toString()Ljava/lang/String;")) { return CGLIB$toString$3$Proxy; } break; case 1984935277: if (var10000.equals("hashCode()I")) { return CGLIB$hashCode$4$Proxy; } } return null; } public Teacher$$EnhancerByCGLIB$$332f7724() { CGLIB$BIND_CALLBACKS(this); } private static final void CGLIB$BIND_CALLBACKS(Object var0) { Teacher$$EnhancerByCGLIB$$332f7724 var1 = (Teacher$$EnhancerByCGLIB$$332f7724)var0; if (!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true; Object var10000 = CGLIB$THREAD_CALLBACKS.get(); if (var10000 == null) { var10000 = CGLIB$STATIC_CALLBACKS; if (var10000 == null) { return; } } var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; } } static { CGLIB$STATICHOOK1(); } }
初始化時,獲得Object的finalize、equals、toString、hashCode、clone以及代理類Teacher的sayHi的方法和方法代理。
看下代理類中的sayHi方法,首先獲取MethodInterceptor實例,如果創(chuàng)建時為空,則調(diào)用super.sayHi(),即代理類中的方法;如果不為空,進行方法攔截,調(diào)用interceptor即2中所示。
類中還有一個sayHi方法,即CGLIB$sayHi$0,里面就是單純的調(diào)用Teacher中的sayHi。那么這個方法是干嘛的呢?看下初始化的最后有這一句
CGLIB$sayHi$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHi", "CGLIB$sayHi$0");
它創(chuàng)建了一個方法代理,并指明了Teacher的sayHi的代理方法是CGLIB$sayHi$0。調(diào)用sayHi并進行方法攔截時,會將CGLIB$sayHi$0$Proxy做為入?yún)魅搿T?的interceptor中,調(diào)用methodProxy.invokeSuper完成方法調(diào)用。
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { this.init(); MethodProxy.FastClassInfo fci = this.fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException var4) { throw var4.getTargetException(); } } //DCL單例模式 private void init() { if (this.fastClassInfo == null) { synchronized(this.initLock) { if (this.fastClassInfo == null) { MethodProxy.CreateInfo ci = this.createInfo; MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo(); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(this.sig1); fci.i2 = fci.f2.getIndex(this.sig2); this.fastClassInfo = fci; this.createInfo = null; } } } }
init方法通過DCL加載fastClassInfo,其中fci.f1為Teacher FastClass,fci.f2為Teacher Enhancer FastClass,也即前面提到的生成三個class文件的另外兩個。
getIndex是根據(jù)方法簽名的hashCode給出的索引值。init完成之后,通過調(diào)用Teacher Enhancer FastClass中的invoke方法,將剛才計算的索引值和入?yún)魅?,這里根據(jù)索引值查到要調(diào)用代理類的CGLIB$sayHi$0,最終調(diào)用了Teacher當中的sayHi。這一套流程下來才算完成。
MethodInterceptor中的invoke和invokeSuper流程上一致,只是它調(diào)用的是Teacher FastClass中的invoke方法,然后調(diào)用sayHi。整個過程比JDK動態(tài)代理要繞,畫個圖總結下
cglib是基于繼承的,所以委托類中的static、private、final方法因為無法繼承所以無法代理。
三、總結
JDK動態(tài)代理基于接口,如果委托類沒有實現(xiàn)接口或者有自定義方法,則無法完成代理。CGLib基于繼承,不受接口的限制,但是不能代理static、private、final方法。
JDK動態(tài)代理是通過反射完成方法調(diào)用,比較消耗性能。CGLib通過建立方法索引,不會有反射帶來的性能問題。
JDK動態(tài)代理只會生成一個代理類。CGLib會生成三個代理類。
兩者都可以用來實現(xiàn)AOP。Spring中兩者均有使用。
到此這篇關于Java中的動態(tài)代理原理及實現(xiàn)的文章就介紹到這了,更多相關Java動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springCloud gateWay 統(tǒng)一鑒權的實現(xiàn)代碼
這篇文章主要介紹了springCloud gateWay 統(tǒng)一鑒權的實現(xiàn)代碼,統(tǒng)一鑒權包括鑒權邏輯和代碼實現(xiàn),本文給大家介紹的非常詳細,需要的朋友可以參考下2022-02-02Netty分布式ByteBuf使用SocketChannel讀取數(shù)據(jù)過程剖析
這篇文章主要為大家介紹了Netty源碼分析ByteBuf使用SocketChannel讀取數(shù)據(jù)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03