jdk動態(tài)代理和cglib動態(tài)代理詳解
如上圖,代理模式可分為動態(tài)代理和靜態(tài)代理,我們比較常用的有動態(tài)代理中的jdk動態(tài)代理和Cglib代理,像spring框架、hibernate框架中都采用了JDK動態(tài)代理,下面將結(jié)合代碼闡述兩種代理模式的使用與區(qū)別。
靜態(tài)代理
靜態(tài)代理的代理對象和被代理對象在代理之前就已經(jīng)確定,它們都實現(xiàn)相同的接口或繼承相同的抽象類。靜態(tài)代理模式一般由業(yè)務(wù)實現(xiàn)類和業(yè)務(wù)代理類組成,業(yè)務(wù)實現(xiàn)類里面實現(xiàn)主要的業(yè)務(wù)邏輯,業(yè)務(wù)代理類負責(zé)在業(yè)務(wù)方法調(diào)用的前后作一些你需要的處理,如日志記錄、權(quán)限攔截等功能…實現(xiàn)業(yè)務(wù)邏輯與業(yè)務(wù)方法外的功能解耦,減少了對業(yè)務(wù)方法的入侵。靜態(tài)代理又可細分為:基于繼承的方式和基于聚合的方式實現(xiàn)。
場景:假設(shè)一個預(yù)減庫存的操作,需要在預(yù)減的前后加日志記錄(我這里是springboot項目)
基于繼承的方式實現(xiàn)靜態(tài)代理
/** * 業(yè)務(wù)實現(xiàn)類接口 */ public interface OrderService { //減庫存操作 void reduceStock(); }
/** * 業(yè)務(wù)實現(xiàn)類 */ @Slf4j public class OrderServiceImpl implements OrderService { @Override public void reduceStock() { try { log.info("預(yù)減庫存中……"); Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } } }
/** * 代理類 */ @Slf4j public class OrderServiceLogProxy extends OrderServiceImpl{ @Override public void reduceStock() { log.info("預(yù)減庫存開始……"); super.reduceStock(); log.info("預(yù)減庫存結(jié)束……"); } }
/** * 測試繼承方式實現(xiàn)的靜態(tài)代理 */ @Test public void testOrderServiceProxy(){ OrderServiceLogProxy proxy = new OrderServiceLogProxy(); proxy.reduceStock(); }
輸出結(jié)果
14:53:53.769 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 預(yù)減庫存開始……
14:53:53.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 預(yù)減庫存中……
14:53:54.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 預(yù)減庫存結(jié)束……
可以看到,OrderServiceLogProxy已經(jīng)實現(xiàn)了為OrderServiceImpl的代理,通過代理類的同名方法來增強了業(yè)務(wù)方法前后邏輯。
基于聚合的方式實現(xiàn)靜態(tài)代理
聚合的意思就是把業(yè)務(wù)類引入到了代理類中,接口和業(yè)務(wù)實現(xiàn)類還是之前的OrderService、OrderServiceImpl,代理類改為如下:
/** * 聚合方式實現(xiàn)靜態(tài)代理:代理類中引入業(yè)務(wù)類 */ @Slf4j public class OrderServiceLogProxy2 implements OrderService { private OrderServiceImpl orderService; public OrderServiceLogProxy2(OrderServiceImpl orderService) { this.orderService = orderService; } @Override public void reduceStock() { log.info("預(yù)減庫存開始……"); orderService.reduceStock(); log.info("預(yù)減庫存結(jié)束……"); } }
/** * 測試聚合方式實現(xiàn)的靜態(tài)代理 */ @Test public void testOrderServiceProxy2() { OrderServiceImpl orderService = new OrderServiceImpl(); OrderServiceLogProxy2 proxy2 = new OrderServiceLogProxy2(orderService); proxy2.reduceStock(); }
測試輸出結(jié)果和上面的結(jié)果是一致的。
繼承與聚合方式實現(xiàn)的靜態(tài)代理對比
結(jié)合上面的代碼來看,如果此時需要疊加代理功能,我不僅要記錄預(yù)減日志,還要增加權(quán)限攔截功能,這個時候如果采用繼承的方式的話,就得新建一個代理類,里面包含日志和權(quán)限邏輯;那要是再增加一個代理功能,又要新增代理類;如果要改變下代理功能的執(zhí)行順序,還是得增加代理類,結(jié)合上面分析來看,這樣做肯定是不妥的。但是如果使用聚合方式的方式呢?我們稍微改造下上面使用的聚合方式實現(xiàn)的靜態(tài)代理代碼:
首先是日志代理類代碼
/** * 聚合方式實現(xiàn)靜態(tài)代理--日志記錄功能疊加改造 */ @Slf4j public class OrderServiceLogProxy3 implements OrderService { //注意,這里換成了接口 private OrderService orderService; public OrderServiceLogProxy3(OrderService orderService) { this.orderService = orderService; } @Override public void reduceStock() { log.info("預(yù)減庫存開始……"); orderService.reduceStock(); log.info("預(yù)減庫存結(jié)束……"); } }
然后是新增的權(quán)限驗證代理類代碼
/** * 聚合方式實現(xiàn)靜態(tài)代理--日志記錄功能疊加改造 */ @Slf4j public class OrderServicePermissionProxy implements OrderService { //注意,這里換成了接口 private OrderService orderService; public OrderServicePermissionProxy(OrderService orderService) { this.orderService = orderService; } @Override public void reduceStock() { log.info("權(quán)限驗證開始……"); orderService.reduceStock(); log.info("權(quán)限驗證結(jié)束……"); } }
測試用例
/** * 測試聚合方式實現(xiàn)的靜態(tài)代理-功能疊加 */ @Test public void testOrderServiceProxy3() { OrderServiceImpl orderService = new OrderServiceImpl(); OrderServiceLogProxy2 logProxy2 = new OrderServiceLogProxy2(orderService); OrderServicePermissionProxy permissionProxy = new OrderServicePermissionProxy(logProxy2); permissionProxy.reduceStock(); }
測試結(jié)果
16:00:28.348 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 權(quán)限驗證開始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 預(yù)減庫存開始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 預(yù)減庫存中……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 預(yù)減庫存結(jié)束……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 權(quán)限驗證結(jié)束……
接下來,如果你需要調(diào)換一下代理類邏輯執(zhí)行順序問題,你只需要在使用(像測試一樣)時調(diào)換一下實例化順序即可實現(xiàn)日志功能和權(quán)限驗證的先后執(zhí)行順序了,而不需要像繼承方式一樣去不斷的新建代理類。
動態(tài)代理
看完上面的靜態(tài)代理,我們發(fā)現(xiàn),靜態(tài)代理模式的代理類,只是實現(xiàn)了特定類的代理,比如上面OrderServiceLogProxy實現(xiàn)的OrderServiceimpl的代理,如果我還有個UserService也許要日志記錄、權(quán)限校驗功能,又得寫雙份的UserServiceLogProxy、UserServicePermissionProxy代理類,里面的邏輯很多都是相同的,也就是說你代理類對象的方法越多,你就得寫越多的重復(fù)的代碼,那么有了動態(tài)代理就可以比較好的解決這個問題,動態(tài)代理就可以動態(tài)的生成代理類,實現(xiàn)對不同類下的不同方法的代理。
JDK動態(tài)代理
jdk動態(tài)代理是利用反射機制生成一個實現(xiàn)代理接口的匿名類,在調(diào)用業(yè)務(wù)方法前調(diào)用InvocationHandler處理。代理類必須實現(xiàn)InvocationHandler接口,并且,JDK動態(tài)代理只能代理實現(xiàn)了接口的類,沒有實現(xiàn)接口的類是不能實現(xiàn)JDK動態(tài)代理。結(jié)合下面代碼來看就比較清晰了。
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * JDK動態(tài)代理實現(xiàn),必須實現(xiàn)InvocationHandler接口 * InvocationHandler可以理解為事務(wù)處理器,所有切面級別的邏輯都在此完成 */ @Slf4j public class DynamicLogProxy implements InvocationHandler { //需要代理的對象類 private Object target; public DynamicLogProxy(Object target) { this.target = target; } /** * @param obj 代理對象 * @param method 對象方法 * @param args 方法參數(shù) * @return * @throws Throwable */ @Override public Object invoke(Object obj, Method method, Object[] args) throws Throwable { log.info("這里是日志記錄切面,日志開始……"); //使用方法的反射 Object invoke = method.invoke(target, args); log.info("這里是日志記錄切面,日志結(jié)束……"); return invoke; } }
使用時代碼
/** * 測試JDK動態(tài)代理實現(xiàn)的日志代理類 */ @Test public void testDynamicLogProxy() { OrderServiceImpl orderService = new OrderServiceImpl(); Class<?> clazz = orderService.getClass(); DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService); //通過Proxy.newProxyInstance(類加載器, 接口s, 事務(wù)處理器Handler) 加載動態(tài)代理 OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler); os.reduceStock(); }
輸出結(jié)果
16:35:54.584 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 這里是日志記錄切面,日志開始……
16:35:54.587 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 預(yù)減庫存中……
16:35:55.587 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 這里是日志記錄切面,日志結(jié)束……
使用JDK動態(tài)代理類基本步驟:
1、編寫需要被代理的類和接口(我這里就是OrderServiceImpl、OrderService
);
2、編寫代理類(例如我這里的DynamicLogProxy
),需要實現(xiàn)InvocationHandler接口,重寫invoke方法;
3、使用Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
動態(tài)創(chuàng)建代理類對象,通過代理類對象調(diào)用業(yè)務(wù)方法。
那么這個時候,如果我需要在代理類中疊加功能,該如何是好?比如不僅要日志,還新增權(quán)限認證,思路還是上面的聚合方式實現(xiàn)靜態(tài)代理里的那樣,貼下代碼,先新增權(quán)限認證代理類
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 基于JDK動態(tài)代理實現(xiàn)的權(quán)限認證代理類 */ @Slf4j public class DynamicPermissionProxy implements InvocationHandler{ private Object target; public DynamicPermissionProxy(Object target) { this.target = target; } /** * @param obj 代理對象 * @param method 對象方法 * @param args 方法參數(shù) * @return * @throws Throwable */ @Override public Object invoke(Object obj, Method method, Object[] args) throws Throwable { log.info("這里是權(quán)限認證切面,開始驗證……"); Object invoke = method.invoke(target,args); log.info("這里是權(quán)限認證切面,結(jié)束驗證……"); return invoke; } }
然后使用時候,需要稍微改動下
/** * 測試JDK動態(tài)代理實現(xiàn)的日志、權(quán)限功能代理類 */ @Test public void testDynamicLogAndPermissProxy() { OrderServiceImpl orderService = new OrderServiceImpl(); Class<?> clazz = orderService.getClass(); DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService); OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler); //注:這里把日志代理類實例對象傳入權(quán)限認證代理類中 DynamicPermissionProxy dynamicPermissionProxy = new DynamicPermissionProxy(os); OrderService os2 = (OrderService)Proxy.newProxyInstance(os.getClass().getClassLoader(),os.getClass().getInterfaces(),dynamicPermissionProxy); os2.reduceStock(); }
如上即可,后面還需要疊加功能代理類的話,按照上面的思路依次傳入代理對象實例即可。
如何實現(xiàn)一個HashMap的動態(tài)代理類?
public class HashMapProxyTest { public static void main(String[] args) { final HashMap<String, Object> hashMap = new HashMap<>(); Map<String, Object> mapProxy = (Map<String, Object>) Proxy.newProxyInstance(HashMap.class.getClassLoader(), HashMap.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(hashMap, args); } }); mapProxy.put("key1", "value1"); System.out.println(mapProxy); } }
Cglib動態(tài)代理
cglib是針對類來實現(xiàn)代理的,它會對目標類產(chǎn)生一個代理子類,通過方法攔截技術(shù)對過濾父類的方法調(diào)用。代理子類需要實現(xiàn)MethodInterceptor接口。另外,如果你是基于Spring配置文件形式開發(fā),那你需要顯示聲明:
<aop:aspectj-autoproxy proxy-target-class="true"/>
如果你是基于SpringBoot開發(fā),則一般在啟動類頭部顯示的添加注解
@EnableAspectJAutoProxy(proxyTargetClass = true)
import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 基于Cglib方式實現(xiàn)動態(tài)代理-日志功能 * 它是針對類實現(xiàn)代理的,類不用實現(xiàn)接口,CGlib對目標類產(chǎn)生一個子類,通過方法攔截技術(shù)攔截所有的方法調(diào)用 */ @Slf4j public class DynamicCglibLogProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxyObj(Class clazz) { //設(shè)置父類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); enhancer.setUseCache(false); return enhancer.create(); } /** * 攔截所有目標類的方法調(diào)用 * * @param o 目標對象 * @param method 目標方法 * @param args 方法參數(shù) * @param methodProxy 代理類實例 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info("這里是日志記錄切面,日志開始……"); //代理類對象實例調(diào)用父類方法 Object result = methodProxy.invokeSuper(o, args); log.info("這里是日志記錄切面,日志結(jié)束……"); return result ; } }
測試用例
/** * 測試Cglib實現(xiàn)的動態(tài)代理-日志功能 */ @Test public void testGclibDynamicLogProxy(){ DynamicCglibLogProxy dynamicCglibLogProxy = new DynamicCglibLogProxy(); OrderServiceImpl orderService = (OrderServiceImpl)dynamicCglibLogProxy.getProxyObj(OrderServiceImpl.class); orderService.reduceStock(); }
輸出結(jié)果
17:41:07.007 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 這里是日志記錄切面,日志開始……
17:41:07.038 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 預(yù)減庫存中……
17:41:08.038 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 這里是日志記錄切面,日志結(jié)束……
JDK與Cglib動態(tài)代理對比?
1、JDK動態(tài)代理只能代理實現(xiàn)了接口的類,沒有實現(xiàn)接口的類不能實現(xiàn)JDK的動態(tài)代理;
2、Cglib動態(tài)代理是針對類實現(xiàn)代理的,運行時動態(tài)生成被代理類的子類攔截父類方法調(diào)用,因此不能代理聲明為final類型的類和方法;
動態(tài)代理和靜態(tài)代理的區(qū)別?
1、靜態(tài)代理在代理前就知道要代理的是哪個對象,而動態(tài)代理是運行時才知道;
2、靜態(tài)代理一般只能代理一個類,而動態(tài)代理能代理實現(xiàn)了接口的多個類;
Spring如何選擇兩種代理模式的?
1、如果目標對象實現(xiàn)了接口,則默認采用JDK動態(tài)代理;
2、如果目標對象沒有實現(xiàn)接口,則使用Cglib代理;
3、如果目標對象實現(xiàn)了接口,但強制使用了Cglib,則使用Cglib進行代理
我們可以結(jié)合源碼來看下,上面的選擇:
補充
Cglib實現(xiàn)的MethodInterceptor接口在spring-core包下,你可能需要要引入
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.0.RELEASE</version> </dependency>
@Slf4j是lombok里提供的,而且如果你在intellij idea開發(fā)工具中使用還需要安裝lombok插件
@Test是Junit包下的,你可能需要引入
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
總結(jié)
本篇文章就到這里了,希望可以給你帶來一些幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot定制三種錯誤頁面及錯誤數(shù)據(jù)方法示例
Spring Boot提供的默認異常處理機制通常并不一定適合我們實際的業(yè)務(wù)場景,因此,我們通常會根據(jù)自身的需要對Spring Boot全局異常進行統(tǒng)一定制,例如定制錯誤頁面,定制錯誤數(shù)據(jù)等。本文主要介紹了SpringBoot三種自定義錯誤頁面的實現(xiàn),快來學(xué)習(xí)吧2021-12-12JDK21新特性Record?Patterns記錄模式詳解(最新推薦)
這篇文章主要介紹了JDK21新特性Record?Patterns記錄模式詳解,本JEP建立在Pattern?Matching?for?instanceof(JEP?394)的基礎(chǔ)上,該功能已在JDK?16中發(fā)布,它與Pattern?Matching?for?switch(JEP?441)共同演進,需要的朋友可以參考下2023-09-09Spring Cloud引入Eureka組件,完善服務(wù)治理
這篇文章主要介紹了Spring Cloud引入Eureka組件,完善服務(wù)治理的過程詳解,幫助大家更好的理解和使用spring cloud,感興趣的朋友可以了解下2021-02-02使用SSM+Layui+Bootstrap實現(xiàn)汽車維保系統(tǒng)的示例代碼
本文主要實現(xiàn)對汽車維修廠的信息化管理功能,。實現(xiàn)的主要功能包含用戶管理、配置管理、汽車管理、故障管理、供應(yīng)商管理、配件管理、維修訂單管理、統(tǒng)計信息、公告管理、個人信息管理,感興趣的可以了解一下2021-12-12