一、代理模式:
靜態代理、動態代理
動態代理和靜態代理區別??
解析:靜態代理需要手工編寫代理類,代理類引用被代理對象。
動態代理是在內存中構建的,不需要手動編寫代理類
代理的目的:是為了在原有的方法上進行增強。
動態代理的兩種方式:JDK動態代理與CGLIB代理
默認情況下,Spring AOP的實現對于接口來說就是使用的JDK的動態代理來實現的,而對于類的代理使用CGLIB來實現。
二、靜態代理
①定義接口 Subject并添加方法:
public interface Subject { public void request(); }
② 定義接口的實現類 RealSubject并實現該接口,重寫方法--被代理對象
public class RealSubject implements Subject{ public void request() { System.out.println("真實主題內容"); }
③定義接口的實現類ProxySubject并實現該接口重寫方法。自定義屬性RealSubject,調用request方法,在這里進行增強
public class ProxySubject implements Subject{ private RealSubject real; public void request() { System.out.println("===before==="); //調用request() real.request(); System.out.println("===after==="); } public RealSubject getReal() { return real; } public void setReal(RealSubject real) { this.real = real; }
④測試類:
分別創建出被代理對象和代理對象,執行方法
public class ProxyTest { //靜態代理 @Test public void test01(){ //準備一個真實主題,被代理對象 RealSubject real=new RealSubject(); //創建一個代理對象 ProxySubject proxy=new ProxySubject(); proxy.setReal(real); proxy.request(); }
執行效果:
可以看出靜態代理類有一個缺點:當如果接口加一個方法,所有的實現類和代理類里都需要做個實現。這就增加了代碼的復雜度。動態代理就可以避免這個缺點。
三、動態代理
1.1、JDK動態代理:
本質:在內存中構建接口實現類
特點:被代理對象必須有接口
JDK提供了java.lang.reflect.Proxy類來實現動態代理的,可通過它的newProxyInstance來獲得代理實現類。同時對于代理的接口的實際處理,是一個java.lang.reflect.InvocationHandler,它提供了一個invoke方法供實現者提供相應的代理邏輯的實現。可以對實際的實現進行一些特殊的處理,像Spring AOP中的各種advice。下面來看看如何使用。
定義接口IUserDao:
public interface IUserDao { public String add(); public String edit(); }
定義接口實現類,實現某接口,并重寫該方法:
public String add() { System.out.println("==add==="); return "我是add"; } public String edit() { System.out.println("===edit==="); return "edit"; }
測試類:(重點)
實現效果:
可以由源碼可見如何生成代理類的:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } // 這里是生成class的地方 Class<?> cl = getProxyClass0(loader, intfs); // 使用我們實現的InvocationHandler作為參數調用構造方法來獲得代理類的實例 try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } } 其中newInstance只是調用Constructor.newInstance來構造相應的代理類實例,這里重點是看getProxyClass0這個方法的實現: private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { // 代理的接口數量不能超過65535 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // JDK對代理進行了緩存,如果已經存在相應的代理類,則直接返回,否則才會通過ProxyClassFactory來創建代理 return proxyClassCache.get(loader, interfaces); }
可以看到,動態生成的代理類有如下特性:
1) 繼承了Proxy類,實現了代理的接口,由于java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。
2) 提供了一個使用InvocationHandler作為參數的構造方法。
3) 生成靜態代碼塊來初始化接口中方法的Method對象,以及Object類的equals、hashCode、toString方法。
4) 重寫了Object類的equals、hashCode、toString,它們都只是簡單的調用了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
1.2、cglib動態代理:
JDK實現動態代理需要實現類通過接口定義業務方法,對于沒有接口的類,如何實現動態代理呢,這就需要cglib了。cglib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用
創建被代理的類:
public class UserService { public void delete(){ System.out.println("delete ok!!!"); } }
測試類:(重點)
該類實現了創建子類的方法與代理的方法。SuperClass方法通過入參即父類的字節碼,通過擴展父類的class來創建代理對象。intercept()方法攔截所有目標類方法的調用,obj表示目標類的實例,method為目標類方法的反射對象,args為方法的動態入參,proxy為代理類實例。proxy.invoke(obj, args)通過代理類調用父類中的方法 !
實現效果:
四、AOP基本術語:
AOP Aspect Oriented Programming 面向切面編程
在軟件業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
面向對象編程是從【靜態角度】考慮程序的結構,而面向切面編程是從【動態角度】考慮程序運行過程。
AOP底層,就是采用【動態代理】模式實現的。采用了兩種代理:JDK動態代理和CGLIB動態代理。
基本術語(一些名詞):
(1)切面(Aspect)
切面泛指[*交叉業務邏輯*]。事務處理和日志處理可以理解為切面。常用的切面有通知(Advice)與顧問(Advisor)。實際就是對主業務邏輯的一種增強。
(2)織入(Weaving)
織入是指將切面代碼插入到目標對象的過程。代理的invoke方法完成的工作,可以稱為織入。
(3) 連接點(JoinPoint)
連接點是指可以被切面織入的方法。通常業務接口的方法均為連接點
(4)切入點(PointCut)
切入點指切面具體織入的方法
注意:被標記為final的方法是不能作為連接點與切入點的。因為最終的是不能被修改的,不能被增強的。
(5)目標對象(Target)
目標對象指將要被增強的對象。即包含主業務邏輯的類的對象。
(6)通知(Advice)
通知是切面的一種實現,可以完成簡單的織入功能。通知定義了增強代碼切入到目標代碼的時間點,是目標方法執行之前執行,還是執行之后執行等。切入點定義切入的位置,通知定義切入的時間。
(7)顧問(Advisor)
顧問是切面的另一種實現,能夠將通知以更為復雜的方式織入到目標對象中,是將通知包裝為更復雜切面的裝配器。
文章列表