對於不使用接口的業務類,無法使用JDK動態代理,cglib采用非常底層的字節碼技術,可以為一個類創建子類,解決無接口代理問題。
動態代理和靜態代理區別??
解析:靜態代理需要手工編寫代理類,代理類引用被代理對象。
動態代理是在內存中構建的,不需要手動編寫代理類。
代理的目的:是為了在原有的方法上進行增強。
動態代理有兩種實現方法:JDK方式和cglib方式。
區別
jdk動態代理的目標對象必須有接口,且創建的代理對象也是需要轉化成接口類型去執行方法,達到增強目標對象行為的目的。這個過程制作快,執行慢。
cglib動態代理以繼承來實現,主要解決沒有接口類的代理實現。這個過程制作慢,執行快。
1.代理模式
代理(Proxy)是一種設計模,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能. 這里使用到編程中的一個思想:在不修改被人代碼的基礎上,可以通過代理的方式來擴展方法,並實現目標對象。
用圖表示如下:
代理模式的關鍵點是:代理對象與目標對象。代理對象是對目標對象的擴展,並會調用目標對象。
1.1.靜態代理(類似於裝飾者模式)
靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類。
案例解釋:
接口:ProxyInterface.java,然后目標對象實現這個接口的方法GoalObject.java,此時如果使用靜態代理方式,就需要在代理對象(StaticProxyObjects.java)中也實現ProxyInterface接口。調用的時候通過調用代理對象的方法來調用目標對象。注意:代理對象與目標對象要實現相同的接口,然后通過調用相同的方法來調用目標對象的方法。
/*** 接口類 */
public interface ProxyInterface { void proxyInterface(); }
/** * 目標對象 *
public class GoalObject implements ProxyInterface{ //實現接口 public void proxyInterface() { System.out.println("實現目標對象功能"); } }
靜態代理對象:
public class StaticProxyObjects implements ProxyInterface{ private GoalObject goalObject; public StaticProxyObjects(GoalObject goalObject) { this.goalObject = goalObject; } //實現方法 public void proxyInterface() { before(); goalObject.proxyInterface(); after(); } private void after() { System.out.println("已經完成代理"); } private void before() { System.out.println("准備開始代理"); } }
測試:
public class StaticProxyTest { /** * 目標對象的實現主要是由代理類對象實現 * @param args */ public static void main(String[] args) { //目標對象 GoalObject goalObject=new GoalObject(); //靜態代理 StaticProxyObjects staticProxyObjects=new StaticProxyObjects(goalObject); staticProxyObjects.proxyInterface(); } }
靜態代理總結: 1.可以做到在不修改目標對象的功能前提下,對目標功能擴展;
2.代理對象持有目標對象引用,重寫構造方法,對目標對象的方法做了增強(實現其他業務功能)。
缺點:(1)代理對象和目標對象都要實現的公共接口;如果接口增加方法,目標對象與代理對象都要維護。
(2)代理角色固定(即:目標對象代碼侵入),一次只能代理一個對象。
動態代理
說明:根據需要通過反射機制在程序運行期動態的為目標對象創建代理對象,代理的行為可以代理多個方法,即滿足生產需要的同時又達到代碼的通用目的。
動態代理有以下特點:
1)繼承了Proxy類,實現了代理的接口,由於java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。但是,代理對象,不需要實現接口。
2)提供了一個使用InvocationHandler作為參數的構造方法。
3)生成靜態代碼塊來初始化接口中方法的Method對象,以及Object類的equals、hashCode、toString方法。
4)重寫了Object類的equals、hashCode、toString,它們都只是簡單的調用了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
jdk的動態代理
本質:在內存中構建出接口的實現類 特點:被代理對象,必須有接口
代理類所在包:java.lang.reflect.Proxy JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次為:
ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的;
Class<?>[] interfaces:目標對象實現的接口的類型,使用泛型方式確認類型
InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入
代碼示例: 接口類ProxyInterface.java以及接口實現類,目標對象GoalObject是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類 (JDKProxy.java),將代理類寫在這個地方,然后在測試類(需要使用到代理的代碼)中先建立目標對象和代理對象的聯系,然后代用代理對象的中同名方法:
接口類:
public interface ProxyInterface { void proxyInterface(); }
目標對象:
public class GoalObject implements ProxyInterface{ //實現接口 public void proxyInterface() { System.out.println("實現目標對象功能"); } }
JDK動態代理:
1)目標對象要有接口,且最后創建的代理對象要轉換成此接口類型,來調用方
2)動態代理類實現InvocationHandler接口,持有目標對象引用,利用構造器動態傳入目標對象
3)使用proxy.newProxyInstance()來動態地創建代理對象
4)代理對象重寫invoke方法,利用反射調用目標對象的方法,同時增加行為
public class JDKProxy implements InvocationHandler{ //目標對象引用 private Object target; //構造器,可以動態傳入目標對象 public JDKProxy(Object target) { this.target = target; } //創建代理對象 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } //利用反射機制調用目標對象的方法,並增強行為 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); //使用反射機制 Object result=method.invoke(target, args); after(); return result; } private void after() { System.out.println("JDK已經完成代理"); } private void before() { System.out.println("JDK准備開始代理"); } }
測試,目標對象的實現主要是由代理類對象實現:
public static void main(String[] args) { //目標對象 GoalObject goalObject=new GoalObject(); //JDK代理對象,使用構造函數傳入對象參數 JDKProxy jDKProxy=new JDKProxy(goalObject); //JDK代理反射機制,獲取對象信息(對象是接口) ProxyInterface proxyInterface=(ProxyInterface) jDKProxy.getProxy(); //通過代理獲取的對象,該對象調用其增強行為 proxyInterface.proxyInterface(); }
總結: 代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用動態代理。
cglib動態代理本質:在內存中生成被代理對象的【子類】 特點:可以在沒有接口的情況下代理
上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.
JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口,如果想代理沒有實現接口的類,就可以使用Cglib實現.
Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
Cglib包的底層是通過使用一個小而塊的字節碼處理框架ASM來轉換字節碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉.
Cglib子類代理實現方法: 1.需要引入cglib的jar文件,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可. 2.引入功能包后,就可以在內存中動態構建子類 3.代理的類不能為final,否則報錯 4.目標對象的方法如果為final/static,那么就不會被攔截,即不會執行目標對象額外的業務方法.
public class CglibObject { public void printInfo(){ System.out.println("打印cglib目標對象"); } }
cglib動態代理:
1)目標對象;
2)實現MethodInterceptor接口,持有目標對象引用,利用構造器動態傳入目標對象;
3)重寫intercept方法,實現行為增強;
public class CglibProxy implements MethodInterceptor{ //目標對象 private Object target; //構造器 public CglibProxy(Object target) { this.target = target; } //動態創建代理對象 public Object getproxy(){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } //利用反射技術,重新intercept使得行為增強 public Object intercept(Object arg0, Method arg1, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invoke(target,objects); after(); return result; } private void after() { System.out.println("cglib已經完成代理"); } private void before() { System.out.println("cglib准備開始代理"); } }
測試,目標對象的實現主要是由代理類對象實現:
public static void main(String[] args) { //Cglib代理對象 CglibProxy cglibProxy=new CglibProxy(new CglibObject()); //Cglib代理反射機制 CglibObject cglibObject=(CglibObject) cglibProxy.getproxy(); cglibObject.printInfo(); }
經驗:在Spring的AOP編程中: 如果加入容器的目標對象有實現接口,用JDK代理;如果目標對象沒有實現接口,用Cglib代理。