設計模式之初識代理模式
學了spring的程式猿都應該知道spring中主要有三個核心,分別是:IOC(控制反轉)、DI(依賴注入)、AOP(面向切面)
而在aop的底層主要就是實現技術就是jdk動態代理和CGlib動態代理這兩種。我之前學習過程中也一直是經常的聽到說介面的動態代理啥啥啥的,
但是到底是個啥,卻是不得而知。然後剛好最近一直都在看設計模式這一塊,其中也就看到了代理模式,所以就決定將這一知識點記錄下來。
1、需要了解程式碼模式,那麼首先就應瞭解,什麼是代理模式?
百度百科:為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。
個人總結:舉個例子,在我們電腦上,一些不用的檔案直接我們直接刪除,被刪除的檔案就都會暫時存放在回收站,這時我們如果要徹底刪除就需要清空回收站,這就是傳統的做法,這樣的做法通常也是存在一個問題,那就是我們僅僅只能夠去刪除我們所能看得見的或者說我們一眼就能看到的所謂的垃圾檔案,但是存在其他碟符路徑比較深的檔案那就很難去發掘了,這個時候我們如果想要做到更好更方便的處理這一事務,那麼我們就可以在電腦上安裝一個“電腦管家”這一類的軟體,來代替我們做清楚電腦中的垃圾檔案,同時還能夠幫我們自動的清楚回收站的垃圾。從程式的角度來簡單理解就是,有了代理模式以後,就能夠在不改變原有的原始碼的情況下, 新增一部分功能,例如框架裡的日誌輸出等功能。
常見的代理就是三種:靜態代理,jdk動態代理,cglib動態代理。
代理模式的組成:
* 抽象介面 (被目標物件和代理物件實現)
* 目標物件 (就是被我們所代理的物件)
* 代理物件 (在完成目標物件中的功能的同時,提供新的功能)
2、程式碼模式如何運用?
本次採用一個模擬使用者登入的案例來演示代理模式。
3、靜態代理模式
3.1 概述
靜態代理模式要求:代理物件和目標物件必需實現同一介面,同時代理物件需要持有目標物件的例項,這樣我們就可以在不修改目標物件原始碼的情況下增加新的功能
3.2 建立一個介面
package com.lxd.dongtaidaili;
/**
* 使用者業務邏輯層介面
*
* @ClassName:IUserService
* @author lxd
* @date 2018年10月31日
* @version
*/
public interface IUserService {
/**
* 登入功能
*
* @Title: login
* @return void
*/
public void login();
}
3.3 建立目標物件(介面的實現類)
package com.lxd.dongtaidaili;
/**
* 使用者業務邏輯層實現類
*
* @ClassName:UserService
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserService implements IUserService {
/** 使用者名稱 */
private String username;
public void setUsername(String username) {
this.username = username;
}
public UserService(String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see com.yidu.dongtaidaili.IUserService#login()
*/
@Override
public void login() {
System.out.println("歡迎使用者:" + username + "登入!");
}
}
3.4 建立代理物件(需要和目標物件實現同一介面)
package com.lxd.dongtaidaili;
/**
* 代理物件
*
* @ClassName:UserServiceProxy
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserServiceProxy implements IUserService {
/** 目標物件,我們需要代理的真實物件 */
private IUserService userService;
/**
* 使用帶參的構造方法給目標物件賦值
* @param userService
*/
public UserServiceProxy(IUserService userService) {
this.userService = userService;
}
@Override
public void login() {
// 簡單提示功能,此處也可以換成其它的業務功能,這裡只做基本演示
System.out.println("警報警報,有身份不明者正準備入侵系統!");
// 呼叫目標物件原有的登入功能
userService.login();
// 簡單提示功能,此處也可以換成其它的業務功能,這裡只做基本演示
System.out.println("警報解除,自己人");
}
}
3.5 建立測試物件
package com.lxd.dongtaidaili;
import org.junit.Test;
/**
* 測試類,測試靜態代理功能
* @ClassName:UserServiceTest
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserServiceTest {
@Test
public void testLogin(){
// 建立代理物件,傳入目標物件的例項
UserServiceProxy proxy = new UserServiceProxy(new UserService("張三"));
// 通過代理物件來呼叫登入功能
proxy.login();
}
}
3.6 測試結果
3.7 靜態代理總結
通過上述的過程我們可以最直觀的看出就是,最終的功能的實現是由代理物件來幫我們完成的。從功能的角度來說,這個方法很不錯,但是也僅僅只是適用於需要代理的地方少,因為代理物件和目標物件都要實現同一介面,這樣的話如果代理物件多了,那麼如果想要做修改,那麼就是一件頭痛的事情,太不利於後期的程式碼維護了。
------------------------------------------------------------------------------------------------------------------------------ 分隔線 ------------------------------------------------------------------------------------------------------------------
4、 JDK動態代理
4.1 概述
* 動態代理和靜態代理最大的區別就在於動態物件中的代理物件不需要和目標物件實現相同的介面,只需要實現JAVA API 提供的代理例項介面即可
* 說到動態代理我們首先需要了解到一個介面以及一個類,檢視java jdk 得到如下解釋:
InvocationHandler:是代理例項的呼叫處理程式 實現的介面。
每個代理例項都具有一個關聯的呼叫處理程式。對代理例項呼叫方法時,將對方法呼叫進行編碼並將其指派到它的呼叫處理程式的 invoke
方法
* invoke(Object proxy, Method method, Object[] args)
在代理例項上處理方法呼叫並返回結果。
* proxy: 代表代理物件
* method: 代表正在執行的方法
* args: 代表呼叫目標方法時傳入的引數
Proxy:提供用於建立動態代理類和例項的靜態方法,它還是由這些方法建立的所有動態代理類的超類
* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的呼叫處理程式。
* loader:目標物件的類載入器
* interfaces: 目標物件所實現的介面
* h: 代理物件,實現對目標物件中方法的呼叫
4.2 建立一個代理物件的處理程式
* 目標物件和介面就用前面靜態代理中的了
package com.lxd.dongtaidaili; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 建立一個代理物件的處理程式 * * @ClassName:UserServiceHandler * @author lxd * @date 2018年10月31日 * @version */ public class UserServiceHandler implements InvocationHandler { /** 目標物件 */ private Object iuserService; /** 使用有參構造給目標物件賦值 */ public UserServiceHandler(Object iuserService) { this.iuserService = iuserService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("動態代理:警報警報,有身份不明者正準備入侵系統!"); method.invoke(iuserService, args); System.out.println("動態代理:警報解除,自己人"); return null; } }
4.3 建立測試類
package com.lxd.dongtaidaili; import java.lang.reflect.Proxy; import org.junit.Test; /** * 測試類,測試動態代理 * @ClassName:UserTestProxy * @author lxd * @date 2018年10月31日 * @version */ public class UserTestProxy { @Test public void testProxy(){ // 建立目標物件例項 UserService target = new UserService("李四"); // 建立一個代理物件的處理程式例項 UserServiceHandler handler = new UserServiceHandler(target); // 建立目標類的代理類,後續使用代理物件執行方法底層都會被自行的執行我們自定義的handler處理程式中的invoke方法 IUserService userService = (IUserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); // 執行代理類中的方法 userService.login(); } }
4.4 執行結果如下
4.5 動態代理總結
從上述案例可以明顯的看出代理類和目標類測底的分離開來,無論後續目標類中的程式碼如何更改,又或者更換介面等,都將於代理類沒有關係,代理類只需要呼叫即可,不需要了解其內部的程式碼結構。
---------------------------------------------------- 分隔線 ---------------------------------------------------------------------------
學無止境,不可輕言放棄,哪怕每天多一點也好。