目錄
一、主要角色
二、類型劃分
三、靜態代理
示例
缺點
四、動態代理
JDK動態代理
示例
?缺點
CGLib動態代理
導入依賴
示例
五、Spring AOP
????????代理模式是一種結構型設計模式,通過代理對象控制對目標對象的訪問,可在不改變目標對象情況下增強其功能,隱藏實現細節。
一、主要角色
- Subject(業務接口類):可以是抽象類或接口
- RealSubject(業務實現類):具體的業務執行,即被代理的對象
- Proxy(代理類):RealSubject的代理
二、類型劃分
根據代理的創建時期,可分為靜態代理和動態代理
- 靜態代理:由程序員創建或工具自動生成代理類源代碼并編譯,程序運行前代理類的字節碼文件已經存在;
- 動態代理:在程序運行時運用反射機制動態創建而成
為方便下面的講解,我們先提前創建業務接口類和業務實現類
//業務接口類
public interface HouseSubject {void rentHouse();
}
//業務實現類(被代理類)
public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("我是房東,我出租房子");}
}
三、靜態代理
示例
//代理類
public class HouseProxy implements HouseSubject{//中介出售房子前要先有房東的授權private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {System.out.println("我是中介,我開始代理房東出租房子");houseSubject.rentHouse();System.out.println("我是中介,代理結束");}
}
使用代理類執行
public class Main {public static void main(String[] args) {//創建代理對象HouseSubject subject=new HouseProxy(new RealHouseSubject());subject.rentHouse();}
}
缺點
- 為每個被代理類編寫代理類,代碼冗余,隨著業務的發展,類數量膨脹,項目管理難度增大
- 被代理類接口變化時,所有相關代理類都需修改,易出錯,可維護性差
- 只能服務特定被代理類,難以應對新類和多變的額外邏輯需求,缺乏靈活性
四、動態代理
JDK動態代理
示例
自定義JDKInvocationHandler 并重寫invoke(),在invoke()中會調用目標方法,并自定義一些處理邏輯
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;//實現 InvocationHandler接口可以被代理對象的方法進行功能增強
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {//目標類private Object target;public JDKInvocationHandler(Object target) {this.target = target;}/*** 調用目標方法,并對方法進行增強* @param proxy 代理對象* @param method 代理對象需要實現的方法* @param args method方法所對應的參數*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info("JDK動態代理開始");//調用目標函數Object result=method.invoke(target,args);log.info("JDK動態代理結束");return null;}
}
創建代理對象,執行邏輯?
import java.lang.reflect.Proxy;public class Main {public static void main(String[] args) {RealHouseSubject target=new RealHouseSubject();//通過被代理類、被代理類實現的接口、方法調用處理器來創建一個代理類/*** ClassLoader loader 類加載器,用于加載代理對象* Class<?>[] interfaces 被代理類實現的一些接口(jdk只能代理實現了接口的類)* java.lang.reflect.InvocationHandler h 實現了InvocationHandler接口的對象*/HouseSubject proxy=(HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocationHandler(target));//使用代理類proxy.rentHouse();}
}
?缺點
只能代理實現了接口的類,不能代理普通類
CGLib動態代理
導入依賴
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
示例
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {private Object target;public CGLibMethodInterceptor(Object target) {this.target = target;}/**** @param o 被代理的對象* @param method 目標方法* @param objects 方法入參* @param methodProxy 用于調用原始方法* @return* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//代理增強內容System.out.println("CGLib代理開始");//通過反射調用被代理的方法Object retVal=methodProxy.invoke(target,objects);System.out.println("CGLib代理結束");return retVal;}
}
import net.sf.cglib.proxy.Enhancer;public class Main {public static void main(String[] args) {HouseSubject target=new RealHouseSubject();HouseSubject proxy= (HouseSubject)Enhancer.create(target.getClass(),new CGLibMethodInterceptor(target));proxy.rentHouse();}
}
五、Spring AOP
Spring AOP是基于動態代理實現的,動態代理有JDK和CGLIB兩種方式,運行時使用哪種方式與項目配置和代理的對象有關。
proxy-target-class="false"情況下,若代理對象實現接口,默認使用JDK動態代理;若未實現接口,則會用CGLIB動態代理。當然,即便對象實現接口,也能通過xml配置proxy-target-class="true"或在配置類上使用注解@EnableAspectJAutoProxy(proxyTargetClass = true) 強制使用CGLIB動態代理。
Spring Boot 2.X開始默認使用CGLIB代理,也就是proxy-target-class="true"。
二者各有優劣,需依項目實際情況抉擇。JDK動態代理優勢在于基于Java原生支持,無需額外依賴,性能較好,適用于對性能要求高且目標對象有接口的場景;但是只能代理實現了接口的對象。
CGLIB動態代理優勢在于能代理無接口的普通類,功能更強大、靈活性更高;但是需額外注入依賴,且通過繼承創建代理,若類被final修飾則無法代理,生成代理對象的性能開銷相對較大。