舉個栗子:
比如有一家美國大學,面向全世界招生,而我們國內的同學,需要去到某個大學。因為我們所處國內,並不知道這個大學的基本情況。那我們又想去了解,並且進入這個大學。這就衍生處理一個行業,
中介(代理)
。由代理招收學生到給到大學。也就是我們入學的事情交給了代理去完成。
特點:
代理模式:給某一個物件提供一個代理,並由代理物件來控制對真實物件的存取。代理模式是一種結構型設計模式。(逐字理解)
代理模式的結構比較簡單,其核心是代理類,為了讓使用者端能夠一致性地對待真實物件和代理物件,在代理模式中引入了抽象層。
可能這裡還是看的雲裡霧裡的,通過一個demo,來加深我們對於靜態代理的理解
目錄結構
// 等同於 Subject
public interface UserService {
// 定義的業務邏輯
void select();
// 定義的業務邏輯
void update();
}
// 等同於 RealSubject
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("查詢 selectById");
}
public void update() {
System.out.println("更新 update");
}
}
此時,我們的業務邏輯已經實現,但是我們的代理還未定義。我們都知道,代理簡單來說,在不侵入原有業務程式碼的條件下,對其功能增強。
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的物件
public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before(); // 增強操作
target.select(); // 這裡才實際呼叫真實主題角色的方法
after(); // 增強操作
}
public void update() {
before(); // 增強操作
target.update(); // 這裡才實際呼叫真實主題角色的方法
after(); // 增強操作
}
/**
* 在執行方法之前執行
*/
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
/**
* 在執行方法之後執行
*/
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
執行使用者端測試:
public class Client {
public static void main(String[] args) {
// 建立業務處理類
UserService userService = new UserServiceImpl();
// 通過構造方法進行傳入業務處理類到代理物件中 進行功能增強
UserServiceProxy proxy = new UserServiceProxy(userService);
// 代理執行目標方法
proxy.select();
}
}
執行結果:
可以看到通過靜態代理,我們達到了功能增強的目的,而且沒有侵入原始碼,這是靜態代理的一個優點。
雖然靜態代理實現簡單,且不侵入原始碼,但是,當場景稍微複雜一些的時候,靜態代理的缺點也會暴露出來。
如:當需要代理多個類的時候,由於代理物件要實現與目標物件一致的介面,有兩種方式:
如何改進?
這就涉及到Java虛擬機器器的類載入機制了,推薦翻看《深入理解Java虛擬機器器》7.3節 類載入的過程。
Java虛擬機器器類載入過程主要分為五個階段:載入、驗證、準備、解析、初始化。其中載入階段需要完成以下3件事情:
java.lang.Class
物件,作為方法區這個類的各種資料存取入口由於虛擬機器器規範對這3點要求並不具體,所以實際的實現是非常靈活的,關於第1點,獲取類的二進位制位元組流(class位元組碼)就有很多途徑:
*$Proxy
的代理類的二進位制位元組流所以,動態代理就是想辦法,根據介面或目標物件,計算出代理類的位元組碼,然後再載入到JVM中使用。但是如何計算?如何生成?情況也許比想象的複雜得多,我們需要藉助現有的方案。
Java的載入時反射系統
,它是一個用於在Java中編輯位元組碼的類庫; 它使Java程式能夠在執行時定義新類,並在JVM載入之前修改類檔案。為了讓生成的
代理類與目標物件(真實主題角色)保持一致性
,從現在開始將介紹以下兩種最常見的方式:
注:使用ASM對使用者要求比較高,使用Javassist會比較麻煩。
JDK動態代理主要涉及兩個類:
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
,我們仍然通過案例來學習編寫一個呼叫邏輯處理器 LogHandler 類,提供紀錄檔增強功能,並實現 InvocationHandler 介面;在 LogHandler 中維護一個目標物件,這個物件是被代理的物件(真實主題角色);在invoke
方法中編寫方法呼叫的邏輯處理。
public class LogHandler implements InvocationHandler {
private Object target;
//傳入目標物件
public LogHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);// 呼叫 target 的 method 方法
after();
return result; // 返回方法的執行結果
}
// 呼叫invoke方法之前執行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 呼叫invoke方法之後執行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
使用者端測試:
public class Client {
public static void main(String[] args) {
// 1. 建立被代理的物件,UserService介面的實現類
UserService userService = new UserServiceImpl();
// 2. 獲取對應的 ClassLoader
ClassLoader classLoader = UserServiceImpl.class.getClassLoader();
// 3. 獲取所有介面的Class,這裡的UserServiceImpl只實現了一個介面UserService
Class<?>[] interfaces = UserServiceImpl.class.getInterfaces();
// 4. 建立一個將傳給代理類的呼叫請求處理器,處理所有的代理物件上的方法呼叫
// 這裡建立的是一個自定義的紀錄檔處理器,須傳入實際的執行物件 userServiceImpl
InvocationHandler handler = new LogHandler(userService);
// 5. newProxyInstance 建立代理物件
// 引數1:需要傳入一個類載入器 也就是需要代理的類
// 引數2:需要傳入一個介面的Class 也就是代理的類需要實現的介面
// 引數3:需要傳入一個呼叫處理類 也就是呼叫過程程中,對目標方法的增強
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
// 通過代理類 呼叫目標方法
proxy.select();
}
}
執行紀錄檔:
JDK動態代理執行方法呼叫的過程簡圖如下:
public final
修飾,所以代理類只可被使用,不可以再被繼承m + 數位
的格式命名super.h.invoke(this, m1, (Object[])null);
呼叫,其中的 super.h.invoke
實際上是在建立代理的時候傳遞給 Proxy.newProxyInstance
的 LogHandler 物件,它繼承 InvocationHandler 類,負責實際的呼叫處理邏輯這裡就不重複寫文章了,參照大佬的文章。
https://www.cnblogs.com/wyq1995/p/10945034.html