4种实例 advice aop_Java动态代理在Spring的应用:AOP编程与动态代理知识
關于代理模式的話題有很多,在開發中經常用到的應該是靜態代理模式,能很好的去耦合。
動態代理是代理模式的另外一種實現。動態代理的區別在哪里?動態代理有什么好處?
今天我們來分析下這些問題。
回顧靜態代理
之前我們分析過一次靜態代理,
用代理模式優雅地寫代碼
一個典型的代理模式的 Proxy類像下面這樣,
代理模式
RealSubject是需要被代理的對象。我們要在RealSubject之外增加一些處理,就增加一個Proxy類,實現Subject接口,在Proxy類里持有RealSubject的實例。
Client的請求全部打到Proxy實例上,由Proxy實例來控制是否將請求轉發給RealSubject,或者做額外的處理。
代理模式的優點:對于外界來講,完全無感知,耦合性低。
看一下實例代碼:
這里對Integer類做了一層代理。這個類的作用在于,在compareTo請求之上增加了信息的打印輸出。
在實際場景中,如果我們需要對多個方法都要做類似的處理,在每個地方都增加同樣的代碼,就顯得有點不夠優雅了。這時候,可以使用java的動態代理。
常用的動態代理有兩種實現方式:JDK和CGLIBJDK動態代理
看名字就可以知道,這種動態代理是JDK本身就支持的,需要借助java.lang.reflect下的接口來實現。
創建代理對象
要想創建一個代理對象,需要使用Proxy類的newProxyInstance方法。
這個方法有三個參數:類加載器。作為Java安全模型的一部分,對于系統類和從因特網上下載下來的類,可以使用不同的類加載器。Class對象數組,每個元素都是需要實現的接口。調用處理器。
調用處理器
調用處理器是實現了InvocationHandler接口的類對象。在這個接口中只有一個方法:
動態代理類,需要實現此接口,在接口方法的實現里做代理邏輯的處理。
創建動態代理
創建一個動態代理對象的工作如下:獲取 RealSubject上的所有接口列表;確定要生成的代理類的類名,默認為:com.sun.proxy.$ProxyXXXX ;
根據需要實現的接口信息,在代碼中動態創建 該Proxy類的字節碼;
將對應的字節碼轉換為對應的class 對象;創建InvocationHandler 實例handler,用來處理Proxy所有方法調用;
Proxy的class對象以創建的handler對象為參數,實例化一個proxy對象。
接下來我們看一段代碼實現:
動態代理
前面提到,使用動態代理解決靜態代理中重復代碼的問題,其實就像是把全部需要代理執行的函數看成是一個可以動態執行的函數,把這個函數像針線一樣,織入到需要執行的額外代碼中間。如前面的日志輸出,把函數織入到日志輸出的代碼中間。怎樣能把函數動態執行?這就需要用到JAVA的反射技術了,這也是動態代理的關鍵。
JDK動態代理
知道了反射機制可以動態執行類對象,就容易理解動態代理了。在JDK中,已默認提供了動態代理的實現,它的關鍵點也是在于通過反射執行invoke來動態執行方法,主要實現流程如下:
- 實現InvocationHandler,由它來實現invoke方法,執行代理函數
- 使用Proxy類根據類的加載器及接口說明,創建代理類,同時關聯委托類
- 使用代理類執行代理函數,則會調用invoke方法,完成代理
在示例代碼中JdkLogProxyHandler類是日志輸出代理類,代碼如下:
public class ReflectionService { public void doSomething(){ System.out.println(" logging reflection service"); }}在客戶端使用時,需要產生代理類,對的日志輸出,執行如下(執行輸出結果與靜態代理功能一致):
//加載類Class> refClass = Class.forName("me.mason.demo.proxy.refrection.ReflectionService");//生成類對象Object refClassObject = refClass.getConstructor().newInstance();//調用類對象方法Method method = refClass.getDeclaredMethod("doSomething");method.invoke(refClassObject);這里把日志輸出代理作為一類,把函數執行計時作為一類(JdkTimeProxyHandler),關注代理內容本身,而不是針對委托類的函數。這里的日志輸出和函數執行計時,就是切面(后面會提到)。
可以比較一下,使用這種動態代理,與前面靜態代理的區別:
- 代理不是固定在某個接口或固定的某個類,而在根據參數動態生成,不是固定(靜態)的
- 在代理中無需針對接口的函數來一個一個實現,只需要針對代理的功能寫一次即可
- 若有多個函數需要寫日志輸出,代理類無需再做修改,執行函數時會自動invoke來完成,就像把函數織入到代碼中。這樣就解決了前面靜態代理的局限。
JDK動態代理與限制
JDK默認提供的動態代理機制使用起來很簡單方便,但它也有相應的限制,就是只能動態代理實現了接口的類,如果類沒有實現接口,只是單純的一個類,則沒有辦法使用InvocationHandler的方式來動態代理了。此時,就需要用到CGLIB來代理。
CGLIB動態代理
CGLIB(Code Generator Library)是一個強大的、高性能的代碼生成庫。CGLIB代理主要通過對字節碼的操作,為對象引入間接級別,以控制對象的訪問。針對上面沒有實現接口的類,CGLIB主要是通過繼承來完成動態代理的。在使用方法上,主要也是有3個步驟:
- 實現MethodInterceptor接口,在intercept方法中實現代理內容(如日志輸出)
- 使用Enhancer及委托類生成代理類
- 使用代理類執行函數,就會動態調用intercept方法的實現
如下所示是使用CGLIB來實現類的動態代理:
/** * 日志動態代理:cglib實現 **/public class CglibLogProxyInterceptor implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(" cglib dynamic proxy log begin "); Object result = methodProxy.invokeSuper(object, args); System.out.println(" cglib dynamic proxy log begin "); return result; } /** * 動態創建代理 * * @param cls 委托類 * @return */ public static T createProxy(Class cls) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(cls); enhancer.setCallback(new CglibLogProxyInterceptor()); return (T) enhancer.create(); }}從上面代碼可知道,代理類是通過Enhancer設置委托類為父類(setsuperclass),并把當前的intercept方法作為回調,以此創建代理類,在客戶端執行代理時,則會執行回調,從而達到代理效果,客戶端執行如下:
@Testvoid testLogProxy() { CglibService proxy = CglibLogProxyInterceptor.createProxy(CglibService.class); proxy.doAction1(); System.out.println("############"); proxy.doAction2();}動態代理在Spring的應用:AOP
前面提到JDK的默認動態代理和CGLIB動態代理,在Spring中,AOP(面向切面編程)就是使用這兩個技術實現的(如果有實現接口的類使用JDK動態代理,沒有實現接口的類則使用CGLIB)。具體到在Spring應用中,如何使用AOP進行切面編程,示例代碼中使用springboot工程,模擬提供user的增刪改查的REST接口,通過切面對所有Service類的函數統一進行日志輸出。
AOP 概念
關于AOP的概念,從理解這兩個問題開始,即代理發生在什么地方,以什么樣的形式添加額外功能代碼。
- 切面(Aspect):前面提到的日志輸出代理和函數執行計時代理,它們其實都是與業務邏輯無關,只是在各個業務邏輯中都添加功能,這種代理就是切面。將橫切關注點與業務邏輯分離的編程方式,每個橫切關注點都集中在一個地方,而不是分散在多處代碼中。
- 切點(PointCut):明確什么地方需要添加額外功能,這些地方有可能是一類函數(比如有多個函數都需要輸出日志),因此需要使用一定的規則定義是哪一類函數。
- 連接點(JoinPoint):就是具體被攔截添加額外功能的地方,其實就是執行的某一個具體函數,是前面切點定義的其中一個函數。
- 通知(advice):明確以什么樣的形式添加額外功能,可以在函數執行前(before),后(after),環繞(around),函數正常返回后通知(afterReturning)和異常返回后通知(afterThrowing)。
在AOP編程中,上面提到的概念,都有對應的注解進行使用,通過注解,就可以實現切面功能。
AOP編程
引入aop依賴
Springboot有提供aop的starter,添加以下依賴,即可使用AOP相關功能。
org.springframework.boot spring-boot-starter-aop定義切面、切點與通知
本示例的需求是對service包下所有類的全部函數統一進行日志輸出。因此我們定義一個LogAopAspect作為這個日志輸出功能的切面(使用注解@Aspect),使用@Pointcut來確定輸出點的匹配規則是service這個包下所有類的全部函數。當真正某個函數執行時,通過動態代理執行通知(使用注解@Before、@After,@Around等)。具體的輸出動作,也就是在這些通知里。
@Slf4j@Aspect@Componentpublic class LogAopAspect { /** * 切點:對service包中所有方法進行織入 */ @Pointcut("execution(* me.mason.demo.proxy.springaop.service.*.*(..))") private void allServiceMethodPointCut() {} @Before("allServiceMethodPointCut()") public void before() { log.info(" spring aop before log begin ");} @AfterReturning("allServiceMethodPointCut()") public void after() { log.info(" spring aop before log end ");} /** * 環繞通知,需要返回調用結果 */ @Around("allServiceMethodPointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info(" spring aop around log begin "); try { return proceedingJoinPoint.proceed(); } finally { log.info(" spring aop around log end "); } }}通過上面的類定義,即可完成動態代理,而不需要像上面的JDK和GCLIB那樣自己實現接口來操作。
AOP的底層實現依然是使用JDK和CGLIB來實現動態代理的,若類有實現接口則使用JDK,沒有則使用CGLIB。 Pointcut的定義規則是指示器+正則式,指示器有參數定義(agrs),執行方法(execution),指定對象(target),指定類型(within)及相應的注解(使用@開頭)。正則式中*表示任何內容,(..)表示任意參數匹配。示例中execution(* me.mason.demo.proxy.springaop.service.*.*(..))表示對執行方法進行攔截,攔截的是me.mason.demo.proxy.springaop.service包下的所有類的所有函數,返回值不限,參數不限。 環繞通知(Around)需要有返回值來返回連接點執行后的結果。
總結
本文對JAVA的動態代理知識進行了梳理,先從代理模式說起,使用靜態代理實現簡單的外加功能,并通過示例實現JDK和CGLIB兩種動態代理功能,最后結合springboot示例,使用AOP編程,實現對關心的類進行日志輸出的切面功能。通過動態代理,我們可以把一些輔助性的功能抽取出來,在不修改業務邏輯的情況下,完成輔助功能的添加。所以當你需要添加新功能,又不想修改原代碼的情況下,就用動態代理吧!
認真寫文章,用心做分享。公眾號:Java耕耘者 文章都會在里面更新,整理的資料也會放在里面。
總結
以上是生活随笔為你收集整理的4种实例 advice aop_Java动态代理在Spring的应用:AOP编程与动态代理知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: flink启动命令参数_Flink集群部
- 下一篇: numpy在折线图上添加取值_见识mat