您好,登錄后才能下訂單哦!
這篇文章給大家介紹如何用最通俗的方法講spring中的AOP,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
自己寫這個系列的目的,是因為自己是個比較笨的人,我曾一度懷疑自己的智商不適合干編程這個行業.因為在我自學的過程中,看過無數別人的文章,博客,心得.可那一大堆的術語,技術基礎,根本就難以閱讀理解.再加上網上文章良莠不齊,很多文章都是寫給已經懂的人看的. 一個例子: Q:什么是控制反轉. A:將對在自身對象中的一個內置對象的控制反轉,反轉后不再由自己本身的對象進行控制這個內置對象的創建,而是由第三方系統去控制這個內置對象的創建. ??? 這種專業的解釋,在我看來就不是給學習的人看的,而是給已經有相當經驗的人讀的. 難道不能用更加感性的解釋這些東西嗎? 編程語言作為一門語言,我認為一定程度上是相當感性的東西,既然我們互相說話時可以聽懂,那么,這些技術就可以在一定程度上,完全可以用感性的方式解釋.
AOP是面向切面. 得,說了白說. 什么是面向切面?這是個問題,但我們先不著急回答.
我們的代碼結構,大多都是MVC模式.那我們用MVC舉個簡單的例子. MVC模式的話,最常用的spring + springMVC + Mybatis,大多數是下面的樣子.
┌───────────────┐ | view | 用戶界面 ├───────────────┤ | Controller | 控制層 ├───────────────┤ | Service | 服務層,也叫業務層 ├───────────────┤ | Dao | 持久層,也叫數據訪問層 ├───────────────┤ | Database | 數據庫 └───────────────┘
外加Model作為數據載體在各個層之間傳來傳去 只要是接觸過JavaWeb開發的,上面都看的懂,如果這個不懂暫時還是不要深究這些知識,先以框架應用為主.
這個Service是個UserService,作用只有一個:保存用戶.
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | UserService.add | ───> 新增用戶 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
現在,有需求要我們在Service中增加日志,開發這個功能的同事不在了,經理要求我們來做這個需求. 那么根據面向對象的理念. 調用service方法前要調用一個日志方法,調用完后要調用一個日志方法,那么代碼中就要增加兩行代碼. 于是,代碼成了這樣:
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | log.info... | ───> 調用日志 | UserService.add | ───> 新增用戶 | log.info... | ───> 調用日志 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
這么寫正確嗎? 答案是當然正確,需求解決了,日志正常保存了,領導很高興.
第二天,又有需求要控制service的事務,OK,然后代碼變成了這樣.
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | Transaction | ───> 事務管理 | log.info... | ───> 調用日志 | UserService.add | ───> 新增用戶 | log.info... | ───> 調用日志 | Transaction | ───> 事務管理 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
第三天,項目經理要求我們記錄方法的運行所用時間,小意思,然后代碼變成了這樣
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | beginTime | ───> 開始時間 | Transaction | ───> 事務管理 | log.info... | ───> 調用日志 | UserService.add | ───> 新增用戶 | log.info... | ───> 調用日志 | Transaction | ───> 事務管理 | endTime | ───> 結束時間 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
第四天,客戶要求我們實現新增用戶時的權限校驗,好說,代碼變成了這樣
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | permissions | ───> 校驗權限 | beginTime | ───> 開始時間 | Transaction | ───> 事務管理 | log.info... | ───> 調用日志 | UserService.add | ───> 新增用戶 | log.info... | ───> 調用日志 | Transaction | ───> 事務管理 | endTime | ───> 結束時間 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘ /* 不知道你們怎么想,但我現在,仍然覺得這種寫法挺好的. 通俗易懂,可讀性強到不知道哪里去了. 當然有個前提,這個項目只有這一個方法. */
第五天,開發UserService的同事回來一看,一臉懵逼,明明走的時候只有一行,現在卻有一大堆不知道什么意思的代碼存在. 得了,他在此基礎上開發,增加各種方法,功能. 隨著需求的變動,慢慢的,一百個service都變成了上面那個樣子. 接著記錄日志的功能有了變動,你發現會影響到調用的類,搜了一下整個項目,發現有特么幾千個地方都調用了這個方法. 你心中一萬頭草泥馬掠過,然后提出了辭職. 接著有個新人接手了這個項目,打開一看,這映入眼簾的一大坨屎一樣的是特么什么東西?? 硬著頭皮改了幾版,每個版本都有BUG,領導一怒之下,項目作廢了,重金找了幾個人重新做了.
==那么問題來了== 現在的需求搞砸了可以辭職,項目爛了可以重做. 那世界上第一個遇到這個問題的人,是怎么解決的?技術就是這技術,重做不還是一樣會遇到這些問題嗎?那我還怎么拓展功能呢?難道所有項目最終都會走向這種絕境? 于是前人開始探索:
┌───────────────┐ | Controller | | ┼───────> 拓展方法寫在這? ├───────────────┤ | Service | ├───────────────┤ | ┼───────> 拓展方法寫在這? | Dao | └───────────────┘
這不都是一回事嗎?根本不解決問題.
我等凡夫俗子解決不了,但世界上有的是自帶外掛的人. 于是,有那么幾個人就找到了解決辦法! 既然我要拓展方法,就要在你的方法里寫代碼,那么我能不能在不改你代碼的基礎上來拓展呢?
==比如在這個地方!==
┌───────────────┐ | Controller | ├───────────────┼───────> 比如,寫在這! | Service | └───────────────┘
what the hell ?????? 你是讓我在一個莫須有的地方寫代碼?
當然不是,大神的意思是代碼要這樣寫.
┌───────────────┐ | Controller | ┌───────────────┐ ├───────────────┼──────┤ log.info... | | Service | └───────────────┘ └───────────────┘
但執行的時候,會是這樣
┌───────────────┐ | Controller | ├───────────────┤ | log.info... | | Service | └───────────────┘
what the hell ??????????????????????????????? 這是為什么?
==答案很簡單:== 首先你既然打算了解AOP,那你應該是有一些Java基礎的. 我們都知道Java文件是沒有用的,這就相當于一個txt文件,當程序真正運行的時候,是把Java文件通過編譯器編譯成class文件來運行的. 那么我們好像看到重點了,編譯器! 既然最終運行的是class文件,而class文件又是編譯器生成的,那我們是不是可以在編譯器上動動手腳呢? 沒錯,想要達成上面的效果,就需要一個不同的編譯器. 把日志這段代碼編譯到service中 當然了,這個編譯器不需要我們開發,而是早有人搞定了.(若是我能開發,我早就去谷歌開發者大會上做演講了= =) 這就是大名鼎鼎的 :==ajc編譯器==(他屬于AspectJ框架,可以單獨使用此框架寫個例子,可以加深理解)
1. 想象一下,有這么兩坨東西 ┌────────────────────┐ | Controller.java | ├────────────────────┤ ┌─────────────────┐ | Service.java | | log.info.java | └────────────────────┘ └─────────────────┘ 2. 然后開始編譯 : 編譯器開始把Service.java中的代碼編織成一個class文件. 3. ajc編譯器開始編譯 : ajc開始把log.info.java編織插入到service中. ┌────────────────────┐ | Controller.java | _________________ ├────────────────────┤ _/ log.info.java | | Service.java | \ ________________| └────────────────────┘ 4. 就像上面,把log.info織入到這個這個方法中. 5. 最終形成的calss文件,就是這樣 ┌───────────────┐ | Controller | ├───────────────┤ | log.info... | | Service | └───────────────┘
成了,問題解決了.有了這個編譯器,前面的問題有迎刃而解. ==ps : ajc編譯器并不是唯一解決辦法==
那么問題又又來了,==面向切面是什么?== 也許你已經有答案了.
┌───────────────┐ | Controller | ┌──────────────────────────────────┐ ├───────────────┼──────> | 我們在這個角度寫代碼,就是面向切面. | | Service | | | ├───────────────┼──────> | 我們在這個角度寫代碼,就是面向切面. | | Dao | └──────────────────────────────────┘ └───────────────┘
我們站在那條線的角度寫代碼,就是面向切面. 那條線,是不是將原本的代碼分成了兩個部分呢? 更形象一點
┌───────────────┐ | Controller | └───────────────┘ ───────────────────────────────────── 這條線,像不像把整個代碼切開的一條線呢?那么這條線,就叫切面 ───────────────────────────────────── 如 : 日志調用 ───────────────────────────────────── 如 : 調用時間 ───────────────────────────────────── 如 : 事務管理 ┌───────────────┐ | Service | ├───────────────┤ | Dao | └───────────────┘
上面的每一個如,就是一個切面類,也相當于一個切面,畢竟沒有人規定我們必須只有一個切面不是?
面向切面是一種人們面對難題時的解決方案,他并不是一個新的語言或是新的技術. 他消除了面向對象的一系列編程陋習.或者說解決了面向對象的一些缺點.
到了這里,我相信有些人還是不太懂面向切面的意義或者作用是什么,沒關系,不要氣餒,你一定比我當時可聰明多了.請繼續往下看.
這張圖可能并不太能直觀的表達面向切面意義有多么重大. ┌───────────────┐ | Controller | └───────────────┘ ───────────────────────── ┌───────────────┐ | Service | ├───────────────┤ | Dao | └───────────────┘ 那么,這張圖呢? ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Controller1 | Controller2 | Controller3 | Controller4 | Controller5 | Controller6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ ─────────────────────── 面向切面的方法,可以織入到所有的service中,而不需要改變service的代碼 ───────────────────────── ─────────────────────── 權限校驗 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 日志調用 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 調用時間 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 事務管理 ─────────────────────────────────────────────────────────────────────────────── ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Service1 | Service2 | Service3 | Service4 | Service5 | Service6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ ─────────────────────── 面向切面的方法,可以織入到所有的Dao中,而不需要改變Dao的代碼 ───────────────────────────────── ─────────────────────── 日志調用 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 調用時間 ─────────────────────────────────────────────────────────────────────────────── ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Dao1 | Dao2 | Dao3 | Dao4 | Dao5 | Dao6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ 我只需要聲明一個類,就可以該類中的方法插入到任意類的位置
有些人說,用代理模式就可以解決上面所說的編程時的一系列問題. 沒錯! 面向切面 ─── 是思想. 代理模式 ─── 是思想的實現.
上面畫過了
只講概念,后面講例子
拓展的類 概念 : Aspect 聲明類似于Java中的類聲明,在Aspect中會包含著一些切點以及相應的增強. 人話 : 創建一個類,這個類中的<增強>將插入到<目標對象>代碼中.
調用點 概念 : 表示在程序中明確定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它 joint point. 人話 : 被<切點>所包含的內容, 如切點為 userService.add()方法 : 連接點就是userService.add() 如切點為 com.xxx包下的所有service : com.xxx包下的所有service的任意一個方法 如切點為 com.xxx包下的所有add開頭的方法 : com.xxx包下的所有add開頭的方法
在哪里增強 概念 : 表示一組<連接點>,這些 joint point 或是通過邏輯關系組合起來,或是通過通配、正則表達式等方式集中起來,它定義了相應的增強將要發生的地方.或是我們要織入的地方 人話 : 要往哪里插入 如 userService.add()方法 或 com.xxx包下的所有service方法 或 com.xxx包下的所有add開頭的方法
拓展方法 概念 : Advice 定義了在<切點>里面定義的程序點具體要做的操作,它通過 before、after 和 around 來區別是在每個<連接點>之前、之后還是代替執行的代碼. 人話 : 切面類中需要定義的,連接點方法調用前的拓展,之后的拓展,或者之前和之后的拓展.
被增強的目標 概念 : 增強(Advice) 的目標對象.. 人話 : 被增強的目標
插入的動作 概念 : 將 Aspect 和其他對象連接起來, 并創建 Adviced object 的過程. 人話 : 將切面類插入進去
例如: 我們要在service的add方法里里增加日志 ┌───────────────┐ | Controller | └───────────────┘ ───────────────────────────────────── <-- com.xxx.service.UService ─── <切點> ┌───────────────┐ ┌────────────┐ <-- log.info所在的類 ─── <切面> | UService.add | | log.info | <-- log.info方法就是 ─── <增強> ├───────────────┤ └────────────┘ | Dao | └───────────────┘ UService.add的調用就是 ─── <連接點> UService ─── <目標對象>
到這里你應該理解什么是面向切面了,如果你還是不理解,不怕,可能是我的表達方式不適合你,全網有很多對于面向對象的優秀理解. 雖然篩選好文章需要時間,我可以幫你找出來,但其實在篩選的過程中,更是一個理解的過程.
關于如何用最通俗的方法講spring中的AOP就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。