兩種動態(tài)代理方式及其區(qū)別(代理模式)

什么是動態(tài)代理?

假如我有一個user對象,該對象里面有4個方法,增、刪、改、查,外界能直接調用這4個方法嗎?拿百度來說,你能隨便對百度上的內容進行增、刪、改、查操作嗎?你最多能執(zhí)行查的操作,增、刪、改的操作是不能執(zhí)行的,你必須要加一個權限操作,應該看看你是否有權限執(zhí)行這個操作。同理,誰操作了這個東西,你需要給我留下記錄,免得我不知道是誰做的。所以,我應該在每一個方法的前面加權限校驗,在每一個方法的后面加日志記錄。

該怎么做呢?

有人說,很簡單,直接在user對象的實現(xiàn)類里面去改,在增、刪、改查前面加上權限校驗,在后面加上日志記錄。你能隨便改別人的代碼嗎?你一改,所以用過user對象的地方都要改,這不亂套了嗎?

有人說,可以再重新創(chuàng)建一個user對象,在新對象中加上權限校驗和日志記錄。確實是這樣。但是如果我還有一個學生類,還有一個老師類...等等,你每一個都新創(chuàng)建一個對象的話,太麻煩了,而且沒有必要,因為對我來說,我只關心對象的增、刪、改、查操作,對于權限校驗和日志記錄我并不關心,這個時候,我們可以找中介來做權限校驗和日志記錄的事情,這個中介就是動態(tài)代理對象!

動態(tài)代理

代理:本來應該自己做的事情,卻請了別人來做,被請的人就是代理對象。

舉例:春季回家買票讓人代買

動態(tài)代理:在程序運行過程中產生的這個對象

而程序運行過程中產生對象其實就是反射,所以,動態(tài)代理其實就是通過反射來生成一個代理

在Java中java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口就可以生成動態(tài)代理對象。JDK提供的代理只能針對接口做代理。我們有更強大的代理cglib

Proxy類中的方法創(chuàng)建動態(tài)代理類對象

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

最終會調用InvocationHandler的方法

InvocationHandler

Object invoke(Object proxy,Method method,Object[] args)

代碼如何來實現(xiàn)呢?

package itcast_06;

/*
 * 用戶操作接口
 */
public interface UserDao {

public abstract void add();

public abstract void delete();

public abstract void update();

public abstract void find();

}

package itcast_06;

/**
* 用戶接口實現(xiàn)類
*/

public class UserDaoImpl implements UserDao {

@Override
public void add() {
System.out.println("添加功能");
}

@Override
public void delete() {
System.out.println("刪除功能");
}

@Override
public void update() {
System.out.println("修改功能");
}

@Override
public void find() {
System.out.println("查找功能");
}

}
package itcast_06;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

//寫一個類實現(xiàn)InvocationHandler接口
public class MyInvocationHandler implements InvocationHandler {

private Object target; // 目標對象

public MyInvocationHandler(Object target) {
this.target = target;
}

//重寫invoke()方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("權限校驗");
Object result = method.invoke(target, args);
System.out.println("日志記錄");
return result; // 返回的是代理對象
}

}

 
package itcast_06;

import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {
UserDao ud = new UserDaoImpl();
ud.add();
ud.delete();
ud.update();
ud.find();
System.out.println("-----------");
// 我們要創(chuàng)建一個動態(tài)代理對象
// Proxy類中有一個方法可以創(chuàng)建動態(tài)代理對象
// public static Object newProxyInstance(ClassLoader loader,Class<?>[]
// interfaces,InvocationHandler h)
// 我準備對ud對象做一個代理對象

MyInvocationHandler handler = new MyInvocationHandler(ud);
UserDao proxy = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), handler);

proxy.add();

proxy.delete();

proxy.update();

proxy.find();

}

}

以上為JDK動態(tài)代理,只能針對接口做代理。我們有更強大的代理cglib。

JDK動態(tài)代理依賴一個類和一個接口,分別是什么?

答:Proxy類和InvocationHandler接口。

調用Proxy類中的newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法可以創(chuàng)建一個動態(tài)代理對象,但是這個方法需要3個參數(shù),前兩個參數(shù)是固定的,但第三個參數(shù)比較麻煩,需要我們創(chuàng)建一個類MyInvocationHandler來實現(xiàn)InvocationHandler接口,這個類里面要重寫invoke()方法。

JDK動態(tài)代理和cglib動態(tài)代理有什么區(qū)別?

答:JDK動態(tài)代理智能對實現(xiàn)了接口的類生成代理對象;

cglib可以對任意類生成代理對象,它的原理是對目標對象進行繼承代理,如果目標對象被final修飾,那么該類無法被cglib代理。

Spring框架的一大特點就是AOP,SpringAOP的本質就是動態(tài)代理,那么Spring到底使用的是JDK代理,還是cglib代理呢?

答:混合使用。如果被代理對象實現(xiàn)了接口,就優(yōu)先使用JDK代理,如果沒有實現(xiàn)接口,就用用cglib代理。

動態(tài)代理的應用

AOP(Aspect-OrientedProgramming,面向切面編程),AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現(xiàn)方式就是通過對目標對象的代理在連接點前后加入通知,完成統(tǒng)一的切面操作。

實現(xiàn)AOP的技術,主要分為兩大類:

  • 一是采用動態(tài)代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執(zhí)行;

  • 二是采用靜態(tài)織入的方式,引入特定的語法創(chuàng)建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據(jù)AdvisedSupport對象的配置來決定。

默認的策略是如果目標類是接口,則使用JDK動態(tài)代理技術,如果目標對象沒有實現(xiàn)接口,則默認會采用CGLIB代理。

如果目標對象實現(xiàn)了接口,可以強制使用CGLIB實現(xiàn)代理(添加CGLIB庫,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。

JDK動態(tài)代理

1、因為利用JDKProxy生成的代理類實現(xiàn)了接口,所以目標類中所有的方法在代理類中都有。
2、生成的代理類的所有的方法都攔截了目標類的所有的方法。而攔截器中invoke方法的內容正好就是代理類的各個方法的組成體。
3、利用JDKProxy方式必須有接口的存在。
4、invoke方法中的三個參數(shù)可以訪問目標類的被調用方法的API、被調用方法的參數(shù)、被調用方法的返回類型。

cglib動態(tài)代理

1、 CGlib是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現(xiàn)Java接口。
2、 用CGlib生成代理類是目標類的子類。
3、 用CGlib生成 代理類不需要接口
4、 用CGLib生成的代理類重寫了父類的各個方法。
5、 攔截器中的intercept方法內容正好就是代理類中的方法體

spring兩種代理方式

  1. 若目標對象實現(xiàn)了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
    優(yōu)點:因為有接口,所以使系統(tǒng)更加松耦合
    缺點:為每一個目標類創(chuàng)建接口

  2. 若目標對象沒有實現(xiàn)任何接口,spring使用CGLIB庫生成目標對象的子類。
    優(yōu)點:因為代理類與目標類是繼承關系,所以不需要有接口的存在。
    缺點:因為沒有使用接口,所以系統(tǒng)的耦合性沒有使用JDK的動態(tài)代理好。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容