前面已經對Spring AOP作了個簡單介紹,今天來分析一下Spring AOP的原理 -- JDK和Cglib代理。java
Spring AOP的原理分爲三部分,概述、設計模式和實現,見下圖:spring
一、原理概述:織入的時機分爲三種,分別是:編程
1)編譯期(AspectJ)設計模式
2)類加載時(AspectJ 5+)數組
3)運行時(Spring AOP )app
運行時織入是怎麼實現的?經過代理對象來實現的,代理分爲動態代理和靜態代理,其中基於動態代理的實現分爲兩種,一種是基於接口代理與基於繼承代碼實現,接下來會重點分析這兩種實現方式。框架
二、設計模式ide
1)Spring AOP中用到了責任鏈模式和代理模式,Spring AOP中的責任鏈模式主要是應用在AOP的鏈式調用上。源碼分析
2)代理模式ui
(1)代理模式定義:代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即經過代理對象訪問目標對象.這樣作的好處是:能夠在目標對象實現的基礎上,加強額外的功能操做,即擴展目標對象的功能.
(2)代理模式做用:爲其餘對象提供一種代理以便控制對這個對象的訪問。
(3)代理分類:代理模式分爲兩種,見下圖:
靜態代碼缺點:要代理的方法越多,代碼重複率越高。假設你的目標類有100個方法,那麼你的代理類裏面就要對這100個方法進行委託,但代理類裏面每一個方法執行的先後代碼都是一致的,因此代碼重複性高。
動態代理的兩類實現:基於接口代理和基於繼承代理(兩個的表明分別是JDK代理和Cglib代理)。
代理模式通常涉及到的角色有:
下面是代理模式的類圖:
類圖說明:這個類圖中有兩個部分須要注意的:
(1)因爲目標對象中有的方法在代理模式中都要有,因此目標類和代理類實現了同一個接口,這體現了面向對象編程的面向接口編程。
(2)代理類中引用了真實目標對象,AOP就是在代理類在調用真實目標對象以前或以後或其它時機作了些額外的工做(好比打印日誌等非功能性的功能)。也就是說代理對象把真正的方法委託給目標對象去執行,而本身就去執行一些額外的邏輯,就是Aop要織入的代碼,從而實現切面切入。
三、實現:
1)JDK代理的實現:
(1)實現要點:
(2)下面經過有一個例子來講明JDK代理
①定義一個普通類:
/** * 普通接口 */ public interface Subject { void request(); void hello(); }
②定義一個實現接口的類:
/** * 真實目標類 */ public class RealSubject implements Subject{ @Override public void request() { System.out.println("RealSubject calls the method of request now ……"); } @Override public void hello() { System.out.println("RealSubject calls the method of hello now ……"); } }
③ 自定義InvocationHandler,經過反射機制調用目標對象中的方法:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import cn.exercise.patten.proxy.RealSubject; /** * 自定義InvocationHandler */ public class JdkProxySubject implements InvocationHandler{ private RealSubject realSubject; // 目標對象 public JdkProxySubject(RealSubject realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--- jdkProxy before ---"); Object result = null; try { result = method.invoke(realSubject, args); // 利用反射調用目標對象的方法 } catch (Exception e) { System.out.println("ex: " + e.getMessage()); } finally { System.out.println("--- jdkProxy after ---"); System.out.println(); } return result; } }
invoke方法參數說明:
④編寫客戶端類:
import java.lang.reflect.Proxy; import cn.exercise.patten.proxy.RealSubject; import cn.exercise.patten.proxy.Subject; /** * 客戶端類 */ public class Client { public static void main(String[] args) { Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), // 目標對象經過getClass方法獲取類的全部信息後,調用getClassLoader()方法來獲取類加載器。獲取類加載器後,能夠經過這個類型的加載器,在程序運行時,將生成的代理類加載到JVM中,以便運行時須要! new Class[]{Subject.class}, //獲取被代理類的一組口信息,以便於生成的代理類能夠具備代理類接口中的全部方法。 new JdkProxySubject(new RealSubject()));//自定義InvocationHandler subject.request(); subject.hello(); } }
⑤運行結果:
(3)JDK代理源碼分析
接下來咱們看看Proxy.newProxyInstance()這個方法裏面的部分代碼,下圖是JDK源碼的一個時序圖,經過這個時序圖的找到apply()方法,查看裏面的一段重要代碼:
說明:這段代碼的做用是驗證傳入的類加載器載入的Class和你傳入的接口對應的Class是否相同,不相等則拋出異常。紅框框出的部分是經過反射機制,加載每個接口的運行時Class信息,經過接口的名稱,找到類,在接着一步往下執行,生成字節碼,最後生成代理類。
經過System.setProperties()能夠設置保存jdk動態代理生成的字節碼文件。
咱們經過反編譯,來看看經過這種方式生成的代理類是怎麼樣的:
//生成的代理類 public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m3; private static Method m2; private static Method m4; private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void hello() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void request() throws { try { this.h.invoke(this, m4, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("com.imooc.pattern.Subject").getMethod("hello", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m4 = Class.forName("com.imooc.pattern.Subject").getMethod("request", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
由上面的圖能夠看到這種方式是基於接口動態生成代理類的,經過自定義的InvocationHandler裏面的invoke()方法來獲取目標類方法的信息。
2)Cglib代理
JDK實現動態代理須要實現類經過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就須要CGLib了。CGLib採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類(經過ASM字節碼處理框架實現),並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
JDK動態代理的攔截對象是經過反射的機制來調用被攔截方法的,反射的效率比較低,因此Cglib採用了FastClass的機制來實現對被攔截方法的調用。FastClass機制就是對一個類的方法創建索引,經過索引來直接調用相應的方法。
(1)Cglib實現要點
(2)接下來經過一個例子來講明Cglib的使用
①建立真實目標對象類:
/** * 真實目標類 */ public class RealSubject implements Subject{ @Override public void request() { System.out.println("RealSubject calls the method of request now ……"); } @Override public void hello() { System.out.println("RealSubject calls the method of hello now ……"); } }
②建立織入代碼類:
import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 織入代碼類 */ public class DemoMethodInterceptor implements MethodInterceptor{ @Override public Object intercept(Object obj, Method arg1, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("--- before ---"); Object result = null; try { result = proxy.invokeSuper(obj, args); } catch (Exception e) { System.out.println("ex:" + e.getMessage()); } finally { System.out.println("--- after ---"); } return result; } }
說明:Enhancer類是CGLib中的一個字節碼加強器,它能夠方便的對你想要處理的類進行擴展 。MethodInterception是Cglib的接口,經過實現這個接口接口,在intercept中織入想要的非功能性代碼。
③建立客戶端類:
import cn.exercise.patten.proxy.RealSubject; import cn.exercise.patten.proxy.Subject; import net.sf.cglib.proxy.Enhancer; /** * 客戶端 */ public class Client { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); // 傳入目標對象,經過繼承生成代理類 enhancer.setSuperclass(RealSubject.class); // 織入代碼 enhancer.setCallback(new DemoMethodInterceptor()); Subject subject = (Subject) enhancer.create(); //調用方法 subject.hello(); subject.request(); } }
④運行結果
3)JDK與Cglib對比
(1)JDK只能針對有接口的類的接口方法進行動態代理;
(2)Cglib基於繼承來實現代理,沒法對static、final類進行代理;(單一的類是沒有static修飾符的,只有靜態類內部能夠用static修飾;)
(3)Cglib基於繼承來實現代理,沒法對private、static方法進行代理。(對於static方法,它是屬於類的,子類在不重寫的狀況下,是能夠調用的,可是一旦重寫了就沒法調用了,普通的public方法能夠經過super.method()調用,可是static方法不行。)
4)看到這裏,或許你的問題就來了:既然有JDK代理,又有Cglib代理,那Spring AOP怎樣判斷使用JDK代理仍是Cglib代理的?
這就涉及Spring的源碼了,咱們經過下面的spring時序圖,找到DefaultAopProxyFactory類,查看裏面的代碼。時序圖以下:
說明:
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
由上面的源碼,總結SpringAOP是如何選擇代理方式:
(1)若是目標對象實現了接口,則默認採用JDK動態代理;
(2)若是目標對象沒有實現接口,則採用Cglib進行動態代理;
(3)若是目標對象實現了接口,且強制Cglib代理,則使用cglib代理;