口語化答案
好的,面試官。jdk 的動態代理主要是依賴Proxy類 和InvocationHandler 接口。jdk 動態代理要求類必須有接口。在進行實現的時候,首先要定義接口,比如MyService,這個接口就是我們的正常功能的實現。但是希望在不更改MyService 的情況下增加額外功能,那么我們需要定義一個實現InvocationHandler 接口的實現類,同時在方法實現上面增加額外的邏輯。最后通過 Proxy 的 newProxyInstance 將二者結合到一起。就實現了動態代理。
題目解析
大家不要覺得動態代理很難理解,按照這個步驟其實你發現很簡單。記憶的過程和 cglib 對比著看,就很輕松,面試也是屬于常考一點的題目。
面試得分點
InvocationHandler 增強,Proxy 創建代理
題目詳細答案
JDK 動態代理主要依賴于java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口來實現。
實現步驟
定義接口:定義需要代理的接口。
實現接口:創建接口的實現類。
創建調用處理器:實現InvocationHandler接口,并在invoke方法中定義代理邏輯。
創建代理對象:通過Proxy.newProxyInstance方法創建代理對象。
代碼 Demo
假設我們有一個簡單的服務接口MyService和它的實現類MyServiceImpl,我們將通過 JDK 動態代理為MyService創建一個代理對象,并在方法調用前后添加日志。
1. 定義接口
public interface MyService {void performTask();
}
2. 實現接口
public class MyServiceImpl implements MyService {@Overridepublic void performTask() {System.out.println("Performing task");}
}
3. 創建調用處理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class LoggingInvocationHandler implements InvocationHandler {private final Object target;public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Logging before method execution: " + method.getName());Object result = method.invoke(target, args);System.out.println("Logging after method execution: " + method.getName());return result;}
}
4. 創建代理對象并使用
import java.lang.reflect.Proxy;public class MainApp {public static void main(String[] args) {// 創建目標對象MyService myService = new MyServiceImpl();// 創建調用處理器LoggingInvocationHandler handler = new LoggingInvocationHandler(myService);// 創建代理對象MyService proxyInstance = (MyService) Proxy.newProxyInstance(myService.getClass().getClassLoader(),myService.getClass().getInterfaces(),handler);// 調用代理對象的方法proxyInstance.performTask();}
}
詳細解釋
1、 接口定義和實現:
MyService是一個簡單的接口,定義了一個方法performTask。
MyServiceImpl是MyService的實現類,實現了performTask方法。
2、 調用處理器:
LoggingInvocationHandler實現了InvocationHandler接口。它的invoke方法在代理對象的方法調用時被調用。invoke方法接收三個參數:proxy:代理對象。method:被調用的方法。args:方法參數。在invoke方法中,我們在方法調用前后添加了日志打印。
3、 創建代理對象:
使用Proxy.newProxyInstance方法創建代理對象。
newProxyInstance方法接收三個參數:類加載器:通常使用目標對象的類加載器。接口數組:目標對象實現的所有接口。調用處理器:實現了InvocationHandler接口的實例。
4、 使用代理對象:
通過代理對象調用方法時,實際調用的是LoggingInvocationHandler的invoke方法。
在invoke方法中,首先打印日志,然后通過反射調用目標對象的方法,最后再打印日志。
JDK動態代理通俗詳解
面試官您好,關于JDK動態代理,我用一個生活中的例子來幫助理解:
快遞代收點類比
想象你網購了一件商品:
- 商家(MyServiceImpl):實際發貨的人
- 快遞代收點(Proxy):中間代理點
- 代收點規則(InvocationHandler):代收點提供的額外服務(比如驗貨、暫存)
實現步驟詳解
1. 定義服務接口(購物清單)
// 就像網購時商家承諾的服務標準
public interface ShoppingService {void deliverItem(); // 送貨服務String checkQuality(); // 驗貨服務
}
2. 實際商家實現(真實發貨)
public class Amazon implements ShoppingService {@Overridepublic void deliverItem() {System.out.println("亞馬遜發貨中...");}@Overridepublic String checkQuality() {return "正品保障";}
}
3. 創建代收點規則(增值服務)
public class ProxyService implements InvocationHandler {private Object target; // 真實的商家對象public ProxyService(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增強:代收點驗貨if(method.getName().equals("deliverItem")) {System.out.println("【代收點】快遞消毒中...");}// 執行原方法(讓商家正常發貨)Object result = method.invoke(target, args);// 后置增強:簽收服務if(method.getName().equals("deliverItem")) {System.out.println("【代收點】已簽收,短信通知客戶");}return result;}
}
4. 創建代收點(生成代理)
public class Client {public static void main(String[] args) {// 真實商家ShoppingService amazon = new Amazon();// 創建代理規則InvocationHandler handler = new ProxyService(amazon);// 建立代收點(生成代理實例)ShoppingService proxy = (ShoppingService) Proxy.newProxyInstance(amazon.getClass().getClassLoader(),amazon.getClass().getInterfaces(),handler);// 客戶通過代收點購物proxy.deliverItem();System.out.println("驗貨結果:" + proxy.checkQuality());}
}
輸出結果
【代收點】快遞消毒中...
亞馬遜發貨中...
【代收點】已簽收,短信通知客戶
驗貨結果:正品保障
關鍵點說明
- 必須要有接口:就像必須通過電商平臺下單,不能直接找路邊攤
- InvocationHandler是核心:所有增強邏輯都在這里實現
- Proxy.newProxyInstance三要素:
- 類加載器:用原來的就行
- 接口數組:說明要代理哪些服務
- 處理規則:怎么增強這些服務
實際項目應用
在我們電商系統中:
- 支付服務接口用JDK代理添加日志
- 訂單服務接口用JDK代理添加事務
- 商品服務接口用JDK代理做緩存
與CGLIB對比記憶
特性 | JDK動態代理 | CGLIB |
依賴 | 必須實現接口 | 不需要接口 |
原理 | 實現相同接口 | 繼承目標類 |
性能 | 反射調用稍慢 | 直接調用更快 |
適用場景 | Spring默認對接口的代理 | 代理普通類 |
這樣設計既保持了規范性(面向接口編程),又能靈活添加通用功能。