手撕設計模式之房產中介——代理模式
1.業務需求
? 大家好,我是菠菜啊,好久不見,今天給大家帶來的是——代理模式。老規矩,在介紹這期內容前,我們先來看看這樣的需求:我們有一套房產需要出售,但是我們抽不開身去帶客戶看房對接而且獲客方式有限,我們該怎么實現?
2.代碼實現
Talk is cheap,show me your code.
初版實現思路:
? 既然自己沒時間、沒客源,可以找個中間人不就行了,這個中間人就是房產代理。
初版代碼如下:
//房屋售賣接口
public interface HouseSaleService {void saleHouse();
}
//房東類
public class HouseOwner implements HouseSaleService {@Overridepublic void saleHouse() {System.out.println("我是房東,我簽訂售房合同");System.out.println("我是房東,售出房源");}
}
//房產中介類
public class ProxyHouse implements HouseSaleService{private HouseSaleService houseSaleService;public ProxyHouse(HouseSaleService houseSaleService) {this.houseSaleService = houseSaleService;}@Overridepublic void saleHouse() {System.out.println("房產中介——開始帶客戶看房");houseSaleService.saleHouse();System.out.println("房產中介——房源售出,結束");}
}
public class Client {public static void main(String[] args) {HouseSaleService houseSaleService = new HouseOwner();HouseSaleService proxyHouse = new ProxyHouse(houseSaleService);proxyHouse.saleHouse();}
}
輸出結果:
實現代碼結構圖:
這個實現過程其實就是代理設計模式,屬于代理模式中的靜態代理。
3.需求升級
我們現在不是房東了,我們現在是一個中介平臺,平臺上有千千萬萬個房東和中介,如果還是實現上述代理買房的需求,交易的流程是一樣的,該怎么做呢?不會添加那么多房產代理人吧。
//售房調用處理器類
public class HouseSaleInvocationHandler implements InvocationHandler {//房東private Object target;public HouseSaleInvocationHandler(Object target){this.target = target;}//三個參數:代理對象實例、方法對象(Method)和參數數組@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("動態生成房產中介代理——開始帶客戶看房");Object result = method.invoke(target, args);System.out.println("動態生成房產中介代理——房源售出,結束");return result;}//創建動態代理對象public static Object newProxyInstance(Object target){//傳入目標對象的類加載器,目標對象實現的接口,調用處理器return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new HouseSaleInvocationHandler(target));}
}
//客戶端(動態代理)
public class Client2 {public static void main(String[] args) {HouseSaleService houseSaleService = (HouseSaleService)HouseSaleInvocationHandler.newProxyInstance(new HouseOwner());houseSaleService.saleHouse();}
}
? 執行結果:
? 上述代碼可以實現一套代理流程動態為多個房東生成房產代理,無需手動一個個手動創建。有沒有同學覺得很熟悉,看過jdk源碼的同學應該很熟悉(詳細實現過程見下方源碼剖析部分),它是利用java.lang.reflect包下的Proxy和InvocationHandler核心類實現動態代理機制的,又稱JDK的動態代理。實現原理:在運行代碼時利用反射動態生成代理類,將目標方法的調用都轉發到InvocationHandler的invoke方法,利用反射機制執行目標方法并插入增強邏輯。
核心流程如下:
思考:
? 上述代碼只能解決實現過售賣接口的房東需求,那些沒通過實現售賣接口的個人房東好像滿足不了他們的需求,所以又有另一種實現方式。
個人房東代碼改造:
pom依賴:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
//cglib代理類
public class CglibProxyHouse {//獲取代理對象public static Object getInstance(Object target) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());// 設置目標類enhancer.setCallback(new CglibMethodInterceptor());// 設置方法攔截器return enhancer.create();}}
//cglib方法攔截增強類
public class CglibMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("動態生成房產中介代理———開始帶客戶看房-CglibProxyHouse");Object result=methodProxy.invokeSuper(proxy, args);// 調用父類(目標類)的方法System.out.println("動態生成房產中介代理———房源售出,結束-CglibProxyHouse");return result;}
}
//個人房東
public class OtherHouseOwner {public void saleHouse() {System.out.println("我是個人房東,我簽訂售房合同");System.out.println("我是個人房東,售出房源");}
}
//Cglib動態代理測試類
public class Client3 {public static void main(String[] args) {OtherHouseOwner houseOwner = (OtherHouseOwner) CglibProxyHouse.getInstance(new OtherHouseOwner());houseOwner.saleHouse();}
}
運行結果:
? 上述代碼實現了個人房東動態代理賣房需求,個人房東無需實現響應接口。它利用net.sf.cglib.proxy包下的Enhancer和MethodInterceptor核心類實現動態代理機制的,又稱CGLIB動態代理。CGLIB通過繼承目標類并重寫非final方法,在運行時使用ASM字節碼技術動態生成代理子類,將方法調用委托給MethodInterceptor實現增強邏輯,并借助FastClass機制通過方法索引直接調用提升性能。
動態代理對比:
特性 | JDK動態代理 | CGLIB |
---|---|---|
代理方式 | 實現接口 | 繼承目標類 |
性能 | 反射調用,較慢 | FastClass調用,較快 |
依賴 | 無第三方庫 | 需CGLIB庫 |
目標類要求 | 必須實現接口 | 不能是final類/方法 |
初始化開銷 | 小 | 大(生成兩個FastClass) |
方法調用模式 | 通過InvocationHandler | 通過MethodInterceptor |
4.定義
? 代理設計模式是一種結構型設計模式,其核心思想是提供一個代理對象來控制對原始對象的訪問。代理充當原始對象的中間層,允許在訪問原始對象前后添加額外邏輯,而無需修改原始對象本身。
組成部分如下:
-
代理對象(Proxy)
- 實現與原始對象相同的接口
- 持有對原始對象的引用
- 控制客戶端對原始對象的訪問
-
原始對象(Real Subject)
- 實際執行業務邏輯的目標對象
-
抽象接口(Subject)
-
定義代理和原始對象的共同接口,確保二者可互換使用
-
5.應用場景
場景類型 | 典型用途 | 實例 |
---|---|---|
虛擬代理 | 延遲創建開銷大的對象 | 圖片懶加載:代理先顯示占位圖,真正需要時才加載高清圖片 |
遠程代理 | 隱藏遠程調用的復雜性 | RPC框架:代理封裝網絡通信細節,客戶端像調用本地對象一樣調用遠程服務 |
保護代理 | 控制訪問權限 | 權限校驗:代理驗證用戶權限后再允許訪問敏感操作 |
緩存代理 | 緩存昂貴操作的結果 | API請求緩存:代理緩存計算結果,重復請求直接返回結果,避免重復計算 |
日志代理 | 記錄訪問日志 | 審計系統:代理在方法調用前后記錄日志和時間戳 |
智能引用代理 | 管理對象生命周期 | 自動釋放資源:代理統計對象引用計數,引用歸零時自動銷毀對象 |
適用性總結
? 需要隔離客戶端與復雜系統(如遠程服務)
? 需要延遲初始化高開銷對象
? 需添加訪問控制或安全層
? 需透明添加日志、監控等橫切關注點
? 需實現智能引用(如對象池、緩存)
典型場景:
? Spring AOP 的底層機制完全基于代理模式實現,通過動態代理在運行時生成代理對象,將切面邏輯(如日志、事務等)織入目標方法中。其具體實現分為兩種機制:JDK 動態代理和CGLIB 代理,由 Spring 根據目標類的特性自動選擇或通過配置強制指定。目標類實現了接口,使用JDK 動態代理;目標類未實現接口,CGLIB 代理,也可強制指定。
源碼剖析
1.JDK動態代理
1.1 Proxy.newProxyInstance() 入口方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {// 1. 驗證接口和處理器有效性Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();// 2. 獲取或生成代理類Class<?> cl = getProxyClass0(loader, intfs);try {// 3. 獲取代理類構造器final Constructor<?> cons = cl.getConstructor(constructorParams);// 4. 創建代理實例return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {// 異常處理...}
}
1.2 代理類生成機制(getProxyClass0)
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {// 1. 接口數量限制(最多65535個)if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// 2. 從緩存獲取或生成代理類return proxyClassCache.get(loader, interfaces);
}
代理類緩存使用WeakCache實現,核心邏輯在ProxyClassFactory中:
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 1. 生成唯一代理類名long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 2. 生成代理類字節碼byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 3. 定義代理類return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);}
}
1.3 代理類字節碼生成(ProxyGenerator)
generateProxyClass
方法生成代理類的字節碼,其核心邏輯如下:
public static byte[] generateProxyClass(final String name,Class<?>[] interfaces,int accessFlags) {ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);// 真正生成字節碼final byte[] classFile = gen.generateClassFile();// 可選項:保存生成的字節碼到文件(調試用)if (saveGeneratedFiles) {java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {try {FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class");file.write(classFile);file.close();} catch (IOException e) { /* 忽略 */ }return null;}});}return classFile;
}
1.4 生成的代理類結構(反編譯示例)
假設我們代理HouseSaleService接口,生成的$Proxy0.class反編譯后:
public final class $Proxy0 extends Proxy implements HouseSaleService {private static Method m1; // hashCode()private static Method m2; // equals()private static Method m3; // toString()private static Method m4; // saleHouse()static {try {m1 = Class.forName("java.lang.Object").getMethod("hashCode");m2 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("java.lang.Object").getMethod("toString");m4 = Class.forName("com.example.HouseSaleService").getMethod("saleHouse");} catch (NoSuchMethodException e) { /* 處理異常 */ }}public $Proxy0(InvocationHandler h) {super(h); // 調用Proxy的構造函數}public final void saleHouse() {try {// 關鍵:調用InvocationHandler的invoke方法super.h.invoke(this, m4, null);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}// 其他Object方法實現類似...
}
代理對象實現目標接口,方法調用里其實還是調用InvocationHandler的invoke方法。
2.CGLIB動態代理
2.1Enhancer 入口類
創建代理對象通常使用以下代碼:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class); // 設置目標類
enhancer.setCallback(new MyMethodInterceptor()); // 設置回調(攔截器)
TargetClass proxy = (TargetClass) enhancer.create(); // 生成代理對象
2.2 enhancer.create() 方法調用鏈
// Enhancer.java
public Object create() {// 關鍵:不使用參數return createHelper();
}private Object createHelper() {// 1. 驗證回調類型preValidate();// 2. 生成代理類Class<?> proxyClass = createClass();// 3. 創建代理實例return createUsingReflection(proxyClass);
}
2.3 代理類生成(createClass)
// AbstractClassGenerator.java
protected Class<?> createClass() {// 使用字節碼生成器生成字節碼byte[] b = strategy.generate(this);// 定義類return ReflectUtils.defineClass(getClassName(), b, loader);
}
2.4 字節碼生成核心:Enhancer.generateClass
CGLIB通過ASM庫直接操作字節碼,關鍵邏輯在Enhancer.generateClass
方法中:
// Enhancer.java
public void generateClass(ClassVisitor v) {// 1. 創建類結構ClassEmitter ce = new ClassEmitter(v);ce.begin_class(/* 版本號 */, ACC_PUBLIC, getClassName(), getSuperclass(), getInterfaces(), "<generated>");// 2. 添加字段ce.declare_field(ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null);ce.declare_field(ACC_PRIVATE, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null);ce.declare_field(ACC_PRIVATE, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null);// 3. 生成構造函數generateConstructor(ce);// 4. 生成回調設置方法generateSetCallbacks(ce);generateSetCallback(ce);generateGetCallback(ce);// 5. 重寫目標方法for (Method method : getMethods()) {generateMethod(ce, method);}ce.end_class();
}
2.5 方法生成邏輯(generateMethod)
// Enhancer.java
private void generateMethod(ClassEmitter ce, Method method) {// 1. 創建方法簽名Signature sig = ReflectUtils.getSignature(method);// 2. 創建方法體CodeEmitter e = ce.begin_method(ACC_PUBLIC, sig, null);// 3. 獲取回調e.load_this();e.getfield(CALLBACK_FIELD);// 4. 檢查回調是否存在Label noCallback = e.make_label();e.dup();e.ifnull(noCallback);// 5. 準備調用參數e.push(method);e.create_arg_array();e.push(methodProxy);// 6. 調用攔截器e.invoke_interface(METHOD_INTERCEPTOR_TYPE, new Signature("intercept", Type.OBJECT, new Type[]{TYPE_OBJECT, TYPE_METHOD, TYPE_OBJECT_ARRAY, TYPE_METHOD_PROXY}));// 7. 結果處理e.unbox_or_zero(e.getReturnType());e.return_value();// 8. 沒有回調時的處理e.mark(noCallback);e.pop();// 調用原始方法super.generateMethod(ce, method);e.return_value();e.end_method();
}
2.6 生成的代理類結構(反編譯示例)
使用CGLIB代理后生成的代理類大致如下:
public class OtherHouseOwner$$EnhancerByCGLIB$$12345678 extends OtherHouseOwner implements Factory {private MethodInterceptor interceptor;private static final Method CGLIB$saleHouse$0$Method;private static final MethodProxy CGLIB$saleHouse$0$Proxy;static {// 初始化目標方法和代理方法CGLIB$saleHouse$0$Method = ReflectUtils.findMethods(new String[]{"saleHouse", "()V"}).get(0);CGLIB$saleHouse$0$Proxy = MethodProxy.create(OtherHouseOwner.class, OtherHouseOwner$$EnhancerByCGLIB$$12345678.class, "()V", "saleHouse", "CGLIB$saleHouse$0");}// 重寫目標方法public final void saleHouse() {MethodInterceptor tmp = this.interceptor;if (tmp == null) {super.saleHouse(); // 如果未設置攔截器,直接調用父類方法} else {// 調用攔截器的intercept方法tmp.intercept(this, CGLIB$saleHouse$0$Method, new Object[0], CGLIB$saleHouse$0$Proxy);}}// 原始方法的直接調用(避免攔截)final void CGLIB$saleHouse$0() {super.saleHouse();}// 其他方法...
}
如果未設置攔截器,直接調用父類方法;如果有設置方法攔截器,直接回調攔截器方法。
2.7 MethodProxy 工作原理
MethodProxy
是CGLIB高效調用的關鍵,它通過生成兩個FastClass(目標類FastClass和代理類FastClass)實現快速方法調用。
// MethodProxy.java
public Object invoke(Object obj, Object[] args) throws Throwable {try {// 使用目標類的FastClass直接調用原始方法return fastClass.invoke(targetIndex, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}
}public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {// 使用代理類的FastClass調用代理類中的原始方法(即CGLIB$xxx方法)return fastClass.invoke(superIndex, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}
}
2.8 FastClass 機制
FastClass為每個方法分配一個索引(index),通過索引直接調用方法,避免反射開銷。
// 生成的TargetFastClass
public Object invoke(int index, Object obj, Object[] args) {OtherHouseOwner otherHouseOwner = (OtherHouseOwner) obj;switch (index) {case 0:otherHouseOwner.saleHouse();return null;// 其他方法...}
}// 生成的ProxyFastClass
public Object invoke(int index, Object obj, Object[] args) {OtherHouseOwner$$EnhancerByCGLIB proxy = (OtherHouseOwner$$EnhancerByCGLIB) obj;switch (index) {case 0:proxy.CGLIB$saleHouse$0();return null;// 其他方法...}
}
法,避免反射開銷。
// 生成的TargetFastClass
public Object invoke(int index, Object obj, Object[] args) {OtherHouseOwner otherHouseOwner = (OtherHouseOwner) obj;switch (index) {case 0:otherHouseOwner.saleHouse();return null;// 其他方法...}
}// 生成的ProxyFastClass
public Object invoke(int index, Object obj, Object[] args) {OtherHouseOwner$$EnhancerByCGLIB proxy = (OtherHouseOwner$$EnhancerByCGLIB) obj;switch (index) {case 0:proxy.CGLIB$saleHouse$0();return null;// 其他方法...}
}
技術需要沉淀,同樣生活也是~
個人鏈接:博客,歡迎一起交流