帶你理解Spring AOP

AOP概述

  • 在我們的日常開發中,除了正常業務邏輯外,還可能經常會需要在業務邏輯的特定位置加入日志,以便于調試和問題分析。但是這種插入日志的邏輯和業務邏輯間并不存在連續性和依賴性,這種邏輯侵入隨著項目的不斷發展,會導致項目越來越來臃腫,同時也更加難以管理。為了解決這個問題,優秀的前輩們推出了AOP(面向切面編程)理念以及很多優秀的AOP框架,其中比較有代表性的就AspectJ,AspectJ通過擴展java語言實現了AOP框架。當然我們的spring框架也實現了自己的AOP框架(Spring AOP),并在spring 2.0后完美的集成了ApectJ。
  • AOP有很多種實現方式,在最早的時候AOP的實現還是通過靜態代理的方式實現,例如AspectJ通過自己的ajc編譯器在程序編譯階段編譯生成靜態代理類的Class文件以完成相關切面邏輯,不過這種方式不夠靈活,每次有新的接口或邏輯需要使用切面功能都需要修改AspectJ中的切面配置,然后重新啟動項目,無疑這是很煩的。隨著技術的發展,我們有了更多更好的技術實現,這里介紹兩只中常用AOP實現技術,也是我們spring中使用的:
    (1)jdk動態代理:
    java在jdk 1.3后引入了jdk動態代理,可以在運行期間為實現了某個接口的對象動態的是生成代理對象,它是在運行期間通過反射實現的,靈活性很好了,但是較編譯生成Class文件的實現方式,性能差了些,另外所有需要進行代理的對象都必須要實現某個接口,這是動態代理最大的硬傷了。
    (2)cglib動態字節碼增強:
    大家都知道,jvm的類加載器并不在乎class文件如何生成,只要是滿足規范的class文件都能加載運行。一般我們的java應用程序都是通過javac編譯生成class文件,但是只要我們滿足class文件規范,我們完全可以使用cglib等字節碼工具在程序運行期間動態的構建Class文件。在我們從本地的class文件加載類文件并構建對象時,可以使用cglib動態的生成該對象的代理對象。

靜態代理與動態代理

AOP的實現是基于代理模式的,所以我們需要通過了解代理模式來學習AOP,接下來我來介紹下靜態代理和動態代理的實現。
1. 靜態代理:
我們可以看下下面的代碼:

public interface Subject {String getName();
}public class RealSubject implements Subject {public String getName() {return ”東哥真帥!“;}
}public class Proxy implements Subject {private RealSubject realSubject;public Proxy(Subject realSubject){this.realSubject = realSubject;}public String getName() {return realSubject.getName() + ”東哥真是666!“;}
}public class Main {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy. getName();
}

我們可以看到真的是灰常簡單,代理對象和原始對象都實現了Subject,代理對象引用了原始對象,并在接口調用時利用了realSubject的getName()方法,只不過在realSubject返回數據的基礎上加了些文字,沒錯這就是最簡單的靜態代理。但這種模式存在一個致命的問題:getName()函數可能并不僅僅只有Subject接口實現了,其他接口可能也實現了這個函數,例如我再有個Topic接口,它也有這個getName()方法。那么當我的切面需要切到所有實現getName的函數時,我們還需要在為Topic接口實現一套類似上面的代碼,這要搞下去的話累都累死了,為了解決這個問題,我們引出了動態代理。我們接下來看下基于jdk和cglib實現的動態代理。

2. 動態代理:

(1)jdk動態代理代碼:

public class RequestInvocationHandler implements InvocationHandler {private Object target;public RequestInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("getName")){return method.invoke(target, args) + "東哥真是666!";}return null;}}Subject subject = (Subject) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Subject.class},new RequestInvocationHandler(new RealSubject()));subject.getName();Topic topic = (Topic) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Topic.class},new RequestInvocationHandler(new RealTopic()));topic.getName();

jdk動態代理的實現主要依賴于java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。我們可以看出jdk動態代理是靠實現特定接口,我們在平時開發中不可能所有的對象都實現特定的接口,cglib為我們解決了這個問題。

(2)cglib實現動態代理代碼:

    public class RequestInvocationHandler implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("getName")) {return methodProxy.invokeSuper(o, objects) + "東哥真是666!";}return null;}}Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Subject.class);enhancer.setCallback(new RequestInvocationHandler());Subject proxy = (Subject) enhancer.create();proxy.getName();

我們可以看到cglib通過繼承目標對象,并覆寫父類方法來實現動態代理,通過字節碼生成技術在運行期間動態的自動幫我們構建代理對象。由于cglib采用繼承覆寫父類方法實現,所以也存在一定局限,當父類的方法為final、static、private類型時,cglib無法對方法進行代理,只能直接調用目標對象的方法。

AOP組件介紹

在具體了解整個AOP的運行流程前,我們先來看下AOP中幾個比較重要的組件,當然這些術語都是參照ApectJ中的概念。

組件名概念
joinpointAOP功能模塊需要織入的地方, 也有人稱之為系統執行點,在ApectJ中有很多執行點類型,例如方法調用、方法執行、字段設置、類初始化等,但是在Spring Aop目前僅支持方法調用類型的切入點,不過已經能夠我們大部分的業務開發了。
pointcutpointcut就是joinpoint的表述方式,通過pointcut的表達式我們就可以尋找到滿足條件joinpoint。
adviceadvice是切入邏輯的載體,簡單的說就是我們要織入的邏輯代碼,他有很多種類型,包括Before Advice、After Advice等等,不過我們最常用的還是Around Advice,它同時包含了其他幾種的功能。
aspect(advisor)aspect是將joinpoint和pointcut模塊化封裝的AOP概念實體,在我們SpringAop中也稱為advisor,且一般一個advisor僅包含一個joinpoint和一個pointcut。
weaverweaver就是織入器,負責將邏輯織入到joinpoint的具體執行者。
target object被織入的對象

Spring AOP織入原理

我們先來看下偽代碼:

// 新建織入者weaver
ProxyFactory weaver = new ProxyFactory(new Executable());// 新建根據接口名匹配的advisor(aspect)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 設置pointcut,在這里是需要織入的接口名,"execute"相當于pointcut
advisor.setMappedName("execute");
// 設置Advice,ExecutableAdvice中包含了代碼邏輯
advisor.setAdvice(new ExecutableAdvice());// 將advisor傳遞給weaver
weaver.addAdvisor(advisor);
// weaver通過ProxyFactor獲取代理類
Executable proxyObject = weaver.getProxy();proxyObject.execute();

上面的偽代碼實際上已經揭示了AOP的完整流程:
(1)通過advisor包裝pointcut和advice數據
(2)然后weaver利用advisor內的數據來獲取代理對象
通過上面的邏輯分析,我們可以知道,對于SpringAOP的實現,最重要的有兩點即pointcut和advice數據的獲取代理對象的生成,那我們接下來就分為這兩部分著重講解下。

(一)pointcut和advice數據的獲取

其實上面的偽代碼是spring早期的實現方式,如上面代碼所示,當時的pointcut只能是方法名或者對象類型(支持正則表達式),而advice需要通過實現特定的接口來實現,advisor也是通過繼承特定的系統類實現的,最后還需要將bean的信息以xml的形式保存起來以支持IoC。
當然現在沒那么麻煩了,我們看下目前我們的使用方式(基于注解的代理):

@Aspect
@Component
class Aspect {@Pointcut("@annotation(com.xxx.xxx.xxx)")public void servicePointcut() {}@Around("servicePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {xxxxxxxxxxxxxxx;xxxxxxxxxxxxxxx;return xxx;}
}

現在我們有注解支持并且集成了AspectJ,可以使用AspectJ表達式,但底層的實現原理還是一樣的。我們現在使用的weaver在注入到容器后,容器會自動尋找容器內所有標識了@Aspect的advisor(aspect)對象,獲取到advisor(aspect)對象后,會通過反射獲取@Pointcut內的表達式,然后通過AspectJ的類庫解析生成pointcut對象,最后ProxyFactory便可以利用這些數據生成代理對象了,其實原理還是一樣的,只是加了層封裝,更加方便了我們的業務開發。

(二)代理對象的生成

接下來就是最重點的部分了,其實也很簡單,需要大家有足夠的IoC知識,可以看下我的IoC原理文章:深入了解spring IoC
在獲取到了pointcut和advice數據后,weaver便開始了代理對象的構造了。我們在上面的代碼中可以看到weaver的類型是ProxyFactory,ProxyFactory是最簡單基本的一個織入器實現類,目前我們最經常使用的織入器實現類是AnnotationAwareAspectJAutoProxyCreator,不過實現原理都是一樣的:都是基于BeanPostProcessor。我們在IoC原理中介紹過BeanPostProcessor,BeanPostProcessor可以干預Bean的實例化過程,它有點類似于責任鏈,任何一個bean的構建都需要經過這條鏈,只有執行完所有BeanPostProcessor后才會返回實例化的對象。AOP實現了BeanPostProcessor,他在目標對象實例化后利用反射或cglib生成了目標對象的動態代理對象(實現邏輯見上面:jdk動態代理代碼實現/cglib實現動態代理),然后直接將代理對象返回給了使用方。這樣使用方使用的便是經過動態代理后的對象,便實現了AOP的功能。很簡單吧!🙃🙃🙃🙃

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450938.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450938.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450938.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

10.20隨筆

ES6 ECMAScript是一種由Ecma國際(前身為歐洲計算機制造商協會,英文名稱是European Computer Manufacturers Association)通過ECMA-262標準化的腳本程序設計語言。 這種語言在萬維網上應用廣泛,它往往被稱為JavaScript或JScript,但…

極客招募令!兄弟杯區塊鏈極客競技大賽在上海等您來戰!

據悉,由國內首家區塊鏈技術社區區塊鏈兄弟主辦,旺鏈科技、離子鏈、中國云體系產業創新戰略聯盟、無退社區、指旺金科等單位強力支持,HiBlock區塊鏈社區、火球財經、布洛克財經、海豚區塊鏈、區塊網等百家技術社區和媒體通力合作的兄弟杯區塊鏈…

Java中Web程序修改配置文件不重啟服務器的方法

見:http://blog.sina.com.cn/s/blog_69398ed9010191jg.html 另:http://ekisstherain.iteye.com/blog/1701463 jrebel 、JavaRebel是什么,見另一博客:jrebel/JavaRebel 開發環境 1. JDK 2. MyEclipse 3. Tomcat 4. Struts2 5.…

ffmpeg-0.6.3 移植到 windows 開源代碼

ffmpeg-0.6.3開源編碼解碼庫,從linux下移植到windows vs2005,全部開源。 需要 Intel C Compile 和 開源的SDL庫支持,由于 Intel C Compile支持C99語法,所以源代碼改動很小很小。 主要的修改 1:添加了linux中有而wind…

一起嘮嘮分布式鎖

(1)分布式鎖和分布式事務的區別 1.分布式鎖是在集群環境下,用來控制不同機器對全局共享資源的訪問。 2.分布式事務是在集群環境下,用來保證全局事務的一致性,保證多個數據庫的數據整體上能正確的從一個一致性狀態轉到…

luogu2577/bzoj1899 午餐 (貪心+dp)

首先,應該盡量讓吃飯慢的排在前面,先按這個排個序 然后再來決定每個人到底去哪邊 設f[i][j]是做到了第i個人,然后1號窗口目前的總排隊時間是j,目前的最大總時間 有這個i和j的話,再預處理出前i個人的排隊總時間sum[i]&a…

wpf中xps文檔合并功能實現

原文:wpf中xps文檔合并功能實現跟著上一篇的xps文檔套打的文章,近期一直在研究xps打印技術,其中用戶提到了一個需求,要求能夠多頁面進行打印,我的想法是,先生成xps文件,然后將文件讀取出來以后,…

DCT(離散余弦變換(DiscreteCosineTransform))

離散余弦變換(Discrete Cosine Transform,簡稱DCT變換)是一種與傅立葉變換緊密相關的數學運算。在傅立葉級數展開式中,如果被展開的函數是實偶函數,那么其傅立葉級數中只包含余弦項,再將其離散化可導出余弦…

從源碼看ConcurrentHashMap

簡介 ConcurrentHashMap是線程安全的HashMap實現,這里主要研究JDK8后的ConcurrentHashMap,下面是ConcurrentHashMap的簡單結構: ConcurrentHashMap基于HashMap的基本邏輯,通過CAS synchronized 來保證并發安全性。ConcurrentHas…

代碼重構的方法

見:http://blog.csdn.net/u011889786/article/details/51865344 見:http://blog.csdn.net/weiky626/article/details/1602691 一.提取子函數 說白了就是一個大函數里,可以根據不同功能分成幾個小函數,因為說不定,其他…

android 去掉標題欄、狀態欄、橫屏

// 去掉標題欄supportRequestWindowFeature(Window.FEATURE_NO_TITLE);// 全屏、隱藏狀態欄getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 橫屏setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION…

Spring Boot 整合Mybatis (一)

2019獨角獸企業重金招聘Python工程師標準>>> 新建spring-boot項目&#xff0c;相關依賴 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><de…

x264 的 cache詳解

在這里和下一級別的分析中有必要先講一下這個h->mb.cache&#xff08;沒法講&#xff0c;就是cache!&#xff09;。 x264_macroblock_cache_load將參考幀中某位置的&#xff08;重建后&#xff09;數據保存進cache&#xff0c;供參考和反復使用。 x264_macroblock_cache_s…

同步/異步阻塞/非阻塞

平時開發中經常會聽大家說到什么同步阻塞、異步非阻塞等等名詞&#xff0c;這里我談下自己對這兩個名詞的理解&#xff0c;僅僅是個人觀點&#xff0c;并不一定正確。 1.阻塞/非阻塞 我認為判定阻塞還是非阻塞&#xff0c;取決于線程所做的操作是否需要將線程掛起等待。 舉個…

Repeater的使用

1.頁面代碼 如果要分頁&#xff0c;那么頁面開頭必須寫&#xff08;<% Register Src"~/Controls/Page.ascx" TagName"Page" TagPrefix"uc1" %>&#xff09; 并且分頁&#xff0c;頁腳<uc1:Page ID"Page2" runat"server&…

springboot 整合 mongodb實現 批量更新數據

現需求&#xff1a;需要批量將1000個數據先查詢在更新到mongodb&#xff08;如果查詢不到數據&#xff0c;則添加數據&#xff09; 1&#xff1a;工具類BathUpdateOptions 1 import org.springframework.data.mongodb.core.query.Query;2 import org.springframework.data.mong…

【開題報告】基于微信小程序的校園資訊平臺的設計與實現

1.選題背景與意義 隨著移動互聯網的快速發展&#xff0c;微信成為了人們日常生活中不可或缺的工具之一。在校園生活中&#xff0c;學生們對于校園資訊的獲取和交流需求也越來越高。然而&#xff0c;傳統的校園資訊發布方式存在信息不及時、傳播范圍有限等問題&#xff0c;無法…

三種Cache寫入方式原理簡介

三種Cache寫入方式原理簡介 在386以上檔次的微機中&#xff0c;為了提高系統效率&#xff0c;普遍采用Cache&#xff08;高速緩沖存儲器&#xff09;&#xff0c;現在的系統甚至可以擁有多級Cache。Cache實際上是位于CPU與DRAM主存儲器之間少量超高速的靜態存儲器&#xff08;S…

Minor GC和Full GC

我們在日常開發中可能經常會聽大家談論GC&#xff0c;但是其實很多人對GC的種類其實并不是很了解&#xff0c;接下來我們簡單介紹下Minor GC和Full GC及他們的區別。 MinorGC&#xff1a; 也可以叫作新生代GC&#xff0c;指的是發生在新生代的垃圾收集動作。因為新生代中對象大…

linux安裝軟件的幾種方法

見&#xff1a;http://blog.csdn.net/u010509774/article/details/50593231 一、rpm包安裝方式步驟&#xff1a; 1、找到相應的軟件包&#xff0c;比如soft.version.rpm&#xff0c;下載到本機某個目錄&#xff1b; 2、打開一個終端&#xff0c;su -成root用戶&#xff1b; …