什麼是代理模式
爲其他對象提供一種代理以控制對這個對象的訪問。
爲什麼使用代理模式
中介隔離:在某些情況下,一個客戶類不想或者不能直接引用一個委託對象,而代理類對象可以在客戶類和委託對象之間起到中介的作用,其特徵是代理類和委託類實現相同的接口。
開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合代碼設計的開閉原則。代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後對返回結果的處理等。代理類本身並不真正實現服務,而是同過調用委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給項目加入緩存、日誌這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委託類。
代理模式實現原理
代理模式主要包含三個角色,即抽象主題角色(Subject)、委託類角色(被代理角色,Proxied)以及代理類角色(Proxy)
抽象主題角色(Subject):可以是接口,也可以是抽象類
委託類角色(proxied):真實主題角色,業務邏輯的具體執行者
代理類角色(Proxy):內部含有對真實對象RealSubject的引用,負責對真實主題角色的調用,並在真實主題角色處理前後做預處理和後處理
代理模式應用場景
SpringAop、日誌收集、權限控制、過濾器、RPC遠程調用
代理模式創建的方式
靜態代理 和 動態代理
靜態代理
靜態代理是由程序員創建或工具生成代理類的源碼,再編譯代理類。
所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。
一句話,自己手寫代理類就是靜態代理。
基於接口實現方式
//主題(Subject) public interface OrderService { void order(); }
//實現接口 public class OrderServiceImpl implements OrderService { public void order() { System.out.println("用戶下單操作.."); } }
//代理類 public class OrderServiceProxy implements OrderService { /** * 代理對象 */ private OrderService proxiedOrderService; public OrderServiceProxy( OrderService orderService) { this.proxiedOrderService=orderService; } public void order() { System.out.println("日誌收集開始.."); proxiedOrderService.order(); System.out.println("日誌收集結束.."); } }
//測試 public class ClientTest { public static void main(String[] args) { OrderService orderService = new OrderServiceProxy(new OrderServiceImpl()); orderService.order(); } }
接口繼承方式實現
//繼承接口實現類 public class OrderServiceProxy extends OrderServiceImpl { public void order() { System.out.println("日誌收集開始.."); super.order(); System.out.println("日誌收集結束.."); } }
//測試 public class ClientTest { public static void main(String[] args) { OrderService orderService = new OrderServiceProxy(); orderService.order(); } }
動態代理
動態代理是在實現階段不用關心代理類,而在運行階段才指定哪一個對象。
動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成 。
JDK動態代理
JDK動態代理的一般步驟如下:
1.創建被代理的接口和類;
2.實現InvocationHandler接口,對目標接口中聲明的所有方法進行統一處理;
3.調用Proxy的靜態方法,創建代理類並生成相應的代理對象;
//主題()Subject public interface OrderService { void order(); }
//實現接口 public class OrderServiceImpl implements OrderService { public void order() { System.out.println("修改數據庫訂單操作.."); } }
//代理類 public class JdkInvocationHandler implements InvocationHandler { /** * 目標代理對象 */ public Object target; public JdkInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(">>>日誌收集開始>>>>"); // 執行代理對象方法 Object reuslt = method.invoke(target, args); System.out.println(">>>日誌收集結束>>>>"); return reuslt; } /** * 獲取代理對象接口 * * @param <T> * @return */ public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } } JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl()); OrderService proxy = jdkInvocationHandler.getProxy(); proxy.order();
原理分析
1. 獲取代理的生成的class文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
2.使用反編譯工具該Proxy0.class
注意:繼承了Proxy類,實現了代理的接口,由於java不能多繼承,這裏已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。
CGLIB動態代理
Cglib是一個強大的,高性能,高質量的代碼生成類庫。它可以在運行期擴展JAVA類與實現JAVA接口。其底層實現是通過ASM字節碼處理框架來轉換字節碼並生成新的類。大部分功能實際上是ASM所提供的,Cglib只是封裝了ASM,簡化了ASM操作,實現了運行期生成新的class。
CGLIB原理
運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法。在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢植入橫切邏輯。
Cglib優缺點
優點:JDK動態代理要求被代理的類必須實現接口,當需要代理的類沒有實現接口時Cglib代理是一個很好的選擇。另一個優點是Cglib動態代理比使用java反射的JDK動態代理要快
缺點:對於被代理類中的final方法,無法進行代理,因爲子類中無法重寫final函數
CGLIB代理實現
版權@須臾之餘https://my.oschina.net/u/3995125
實現MethodInterceptor接口的intercept方法後,所有生成的代理方法都調用這個方法。
intercept方法的具體參數有
obj 目標類的實例
1.method 目標方法實例(通過反射獲取的目標方法實例) 2.args 目標方法的參數 3.proxy 代理類的實例
該方法的返回值就是目標方法的返回值。
public class OrderServiceImpl { public void order() { System.out.println("用戶下單操作.."); } }
public class CglibMethodInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("<<<<<日誌收集開始...>>>>>>>"); Object reuslt = proxy.invokeSuper(obj, args); System.out.println("<<<<<日誌收集結束...>>>>>>>"); return reuslt; } } System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code"); CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor(); Enhancer enhancer = new Enhancer(); // 設置代理類的付類 enhancer.setSuperclass(OrderServiceImpl.class); // 設置回調對象 enhancer.setCallback(cglibMethodInterceptor); // 創建代理對象 OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create(); orderServiceImpl.order();
Maven依賴
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.12</version> </dependency> </dependencies>
結果
>>>>cglib日誌收集開始....
執行訂單業務邏輯代碼
>>>>cglib日誌收集結束....
靜態代理與動態代理區別
靜態代理需要自己寫代理類,而動態代理不需要寫代理類。
JDK動態代理與CGLIB實現區別
JDK動態代理底層實現:
JDK的動態代理使用Java的反射技術生成動態代理類,只能代理實現了接口的類, 沒有實現接口的類不能實現動態代理。
CGLIB動態代理底層實現:
運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法,在子類中採用方法攔截的技術攔截所有父類方法的調用,不需要被代理類對象實現接口,從而CGLIB動態代理效率比Jdk動態代理反射技術效率要高。
案例:使用AOP攔截Controller所有請求日誌
@Aspect
@Component
@Slf4j
public class AopLogAspect {
// 申明一個切點 裏面是 execution表達式
@Pointcut("execution(* com.xuyu.controller.*.*(..))")
private void serviceAspect() {
}
// 請求method前打印內容
@Before(value = "serviceAspect()")
public void methodBefore(JoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 打印請求內容
log.info("===============請求內容===============");
log.info("請求地址:" + request.getRequestURL().toString());
log.info("請求方式:" + request.getMethod());
log.info("請求類方法:" + joinPoint.getSignature());
log.info("請求類方法參數:" + Arrays.toString(joinPoint.getArgs()));
log.info("===============請求內容===============");
}
// 在方法執行完結後打印返回內容
@AfterReturning(returning = "o", pointcut = "serviceAspect()")
public void methodAfterReturing(Object o) {
log.info("--------------返回內容----------------");
log.info("Response內容:" + o.toString());
log.info("--------------返回內容----------------");
}
}
Maven依賴信息
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
<!-- sprinboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
結果
===============請求內容===============
: 請求地址:http://127.0.0.1:8080/getUser
: 請求方式:GET
: 請求類方法:String com.xuyu.service.controller.IndexController.getUser(String,Integer)
: 請求類方法參數:[xuyu, 2]
: ===============請求內容===============
: >>>userName:{},xuyu
: --------------返回內容----------------
: Response內容:success_getUser
: --------------返回內容----------------
知識分享
本文參考:螞蟻課堂