摘要:基本知識其實接觸了這么久的我感覺給人難以理解的一個關(guān)鍵點是它的概念比較多而且坑爹的是這些概念經(jīng)過了中文翻譯后變得面目全非相同的一個術(shù)語在不同的翻譯下含義總有著各種莫名其妙的差別鑒于此我在本章的開頭著重為為大家介紹一個的各項術(shù)語的基本含義為了
基本知識
其實, 接觸了這么久的 AOP, 我感覺, AOP 給人難以理解的一個關(guān)鍵點是它的概念比較多, 而且坑爹的是, 這些概念經(jīng)過了中文翻譯后, 變得面目全非, 相同的一個術(shù)語, 在不同的翻譯下, 含義總有著各種莫名其妙的差別. 鑒于此, 我在本章的開頭, 著重為為大家介紹一個 Spring AOP 的各項術(shù)語的基本含義. 為了術(shù)語傳達的準(zhǔn)確性, 我在接下來的敘述中, 能使用英文術(shù)語的地方, 盡量使用英文.
什么是 AOPAOP(Aspect-Oriented Programming), 即 面向切面編程, 它與 OOP( Object-Oriented Programming, 面向?qū)ο缶幊? 相輔相成, 提供了與 OOP 不同的抽象軟件結(jié)構(gòu)的視角.
在 OOP 中, 我們以類(class)作為我們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)
aspect 由 pointcount 和 advice 組成, 它既包含了橫切邏輯的定義, 也包括了連接點的定義. Spring AOP就是負(fù)責(zé)實施切面的框架, 它將切面所定義的橫切邏輯織入到切面所指定的連接點中.
AOP的工作重心在于如何將增強織入目標(biāo)對象的連接點上, 這里包含兩個工作:
如何通過 pointcut 和 advice 定位到特定的 joinpoint 上
如何在 advice 中編寫切面代碼.
可以簡單地認(rèn)為, 使用 @Aspect 注解的類就是切面.
advice(增強)由 aspect 添加到特定的 join point(即滿足 point cut 規(guī)則的 join point) 的一段代碼.
許多 AOP框架, 包括 Spring AOP, 會將 advice 模擬為一個攔截器(interceptor), 并且在 join point 上維護多個 advice, 進行層層攔截.
例如 HTTP 鑒權(quán)的實現(xiàn), 我們可以為每個使用 RequestMapping 標(biāo)注的方法織入 advice, 當(dāng) HTTP 請求到來時, 首先進入到 advice 代碼中, 在這里我們可以分析這個 HTTP 請求是否有相應(yīng)的權(quán)限, 如果有, 則執(zhí)行 Controller, 如果沒有, 則拋出異常. 這里的 advice 就扮演著鑒權(quán)攔截器的角色了.
a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
程序運行中的一些時間點, 例如一個方法的執(zhí)行, 或者是一個異常的處理.
在 Spring AOP 中, join point 總是方法的執(zhí)行點, 即只有方法連接點.
匹配 join point 的謂詞(a predicate that matches join points).
Advice 是和特定的 point cut 關(guān)聯(lián)的, 并且在 point cut 相匹配的 join point 中執(zhí)行.
在 Spring 中, 所有的方法都可以認(rèn)為是 joinpoint, 但是我們并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一組規(guī)則(使用 AspectJ pointcut expression language 來描述) 來匹配joinpoint, 給滿足規(guī)則的 joinpoint 添加 Advice.
在 Spring AOP 中, 所有的方法執(zhí)行都是 join point. 而 point cut 是一個描述信息, 它修飾的是 join point, 通過 point cut, 我們就可以確定哪些 join point 可以被織入 Advice. 因此 join point 和 point cut 本質(zhì)上就是兩個不同緯度上的東西.
advice 是在 join point 上執(zhí)行的, 而 point cut 規(guī)定了哪些 join point 可以執(zhí)行哪些 advice
為一個類型添加額外的方法或字段. Spring AOP 允許我們?yōu)?目標(biāo)對象 引入新的接口(和對應(yīng)的實現(xiàn)). 例如我們可以使用 introduction 來為一個 bean 實現(xiàn) IsModified 接口, 并以此來簡化 caching 的實現(xiàn).
目標(biāo)對象(Target)織入 advice 的目標(biāo)對象. 目標(biāo)對象也被稱為 advised object.
因為 Spring AOP 使用運行時代理的方式來實現(xiàn) aspect, 因此 adviced object 總是一個代理對象(proxied object)
注意, adviced object 指的不是原來的類, 而是織入 advice 后所產(chǎn)生的代理類.
一個類被 AOP 織入 advice, 就會產(chǎn)生一個結(jié)果類, 它是融合了原類和增強邏輯的代理類.
在 Spring AOP 中, 一個 AOP 代理是一個 JDK 動態(tài)代理對象或 CGLIB 代理對象.
將 aspect 和其他對象連接起來, 并創(chuàng)建 adviced object 的過程.
根據(jù)不同的實現(xiàn)技術(shù), AOP織入有三種方式:
編譯器織入, 這要求有特殊的Java編譯器.
類裝載期織入, 這需要有特殊的類裝載器.
動態(tài)代理織入, 在運行期為目標(biāo)類添加增強(Advice)生成子類的方式.
Spring 采用動態(tài)代理織入, 而AspectJ采用編譯器織入和類裝載期織入.
before advice, 在 join point 前被執(zhí)行的 advice. 雖然 before advice 是在 join point 前被執(zhí)行, 但是它并不能夠阻止 join point 的執(zhí)行, 除非發(fā)生了異常(即我們在 before advice 代碼中, 不能人為地決定是否繼續(xù)執(zhí)行 join point 中的代碼)
after return advice, 在一個 join point 正常返回后執(zhí)行的 advice
after throwing advice, 當(dāng)一個 join point 拋出異常后執(zhí)行的 advice
after(final) advice, 無論一個 join point 是正常退出還是發(fā)生了異常, 都會被執(zhí)行的 advice.
around advice, 在 join point 前和 joint point 退出后都執(zhí)行的 advice. 這個是最常用的 advice.
關(guān)于 AOP ProxySpring AOP 默認(rèn)使用標(biāo)準(zhǔn)的 JDK 動態(tài)代理(dynamic proxy)技術(shù)來實現(xiàn) AOP 代理, 通過它, 我們可以為任意的接口實現(xiàn)代理.
如果需要為一個類實現(xiàn)代理, 那么可以使用 CGLIB 代理. 當(dāng)一個業(yè)務(wù)邏輯對象沒有實現(xiàn)接口時, 那么Spring AOP 就默認(rèn)使用 CGLIB 來作為 AOP 代理了. 即如果我們需要為一個方法織入 advice, 但是這個方法不是一個接口所提供的方法, 則此時 Spring AOP 會使用 CGLIB 來實現(xiàn)動態(tài)代理. 鑒于此, Spring AOP 建議基于接口編程, 對接口進行 AOP 而不是類.
看完了上面的理論部分知識, 我相信還是會有不少朋友感覺到 AOP 的概念還是很模糊, 對 AOP 中的各種概念理解的還不是很透徹. 其實這很正常, 因為 AOP 中的概念是在是太多了, 我當(dāng)時也是花了老大勁才梳理清楚的.
下面我以一個簡單的例子來比喻一下 AOP 中 aspect, jointpoint, pointcut 與 advice 之間的關(guān)系.
讓我們來假設(shè)一下, 從前有一個叫爪哇的小縣城, 在一個月黑風(fēng)高的晚上, 這個縣城中發(fā)生了命案. 作案的兇手十分狡猾, 現(xiàn)場沒有留下什么有價值的線索. 不過萬幸的是, 剛從隔壁回來的老王恰好在這時候無意中發(fā)現(xiàn)了兇手行兇的過程, 但是由于天色已晚, 加上兇手蒙著面, 老王并沒有看清兇手的面目, 只知道兇手是個男性, 身高約七尺五寸. 爪哇縣的縣令根據(jù)老王的描述, 對守門的士兵下命令說: 凡是發(fā)現(xiàn)有身高七尺五寸的男性, 都要抓過來審問. 士兵當(dāng)然不敢違背縣令的命令, 只好把進出城的所有符合條件的人都抓了起來.
來讓我們看一下上面的一個小故事和 AOP 到底有什么對應(yīng)關(guān)系.
首先我們知道, 在 Spring AOP 中 join point 指代的是所有方法的執(zhí)行點, 而 point cut 是一個描述信息, 它修飾的是 join point, 通過 point cut, 我們就可以確定哪些 join point 可以被織入 Advice. 對應(yīng)到我們在上面舉的例子, 我們可以做一個簡單的類比, join point 就相當(dāng)于 爪哇的小縣城里的百姓, point cut 就相當(dāng)于 老王所做的指控, 即兇手是個男性, 身高約七尺五寸, 而 advice 則是施加在符合老王所描述的嫌疑人的動作: 抓過來審問.
為什么可以這樣類比呢?
join point --> 爪哇的小縣城里的百姓: 因為根據(jù)定義, join point 是所有可能被織入 advice 的候選的點, 在 Spring AOP中, 則可以認(rèn)為所有方法執(zhí)行點都是 join point. 而在我們上面的例子中, 命案發(fā)生在小縣城中, 按理說在此縣城中的所有人都有可能是嫌疑人.
point cut --> 男性, 身高約七尺五寸: 我們知道, 所有的方法(joint point) 都可以織入 advice, 但是我們并不希望在所有方法上都織入 advice, 而 pointcut 的作用就是提供一組規(guī)則來匹配joinpoint, 給滿足規(guī)則的 joinpoint 添加 advice. 同理, 對于縣令來說, 他再昏庸, 也知道不能把縣城中的所有百姓都抓起來審問, 而是根據(jù)兇手是個男性, 身高約七尺五寸, 把符合條件的人抓起來. 在這里 兇手是個男性, 身高約七尺五寸 就是一個修飾謂語, 它限定了兇手的范圍, 滿足此修飾規(guī)則的百姓都是嫌疑人, 都需要抓起來審問.
advice --> 抓過來審問, advice 是一個動作, 即一段 Java 代碼, 這段 Java 代碼是作用于 point cut 所限定的那些 join point 上的. 同理, 對比到我們的例子中, 抓過來審問 這個動作就是對作用于那些滿足 男性, 身高約七尺五寸 的爪哇的小縣城里的百姓.
aspect: aspect 是 point cut 與 advice 的組合, 因此在這里我們就可以類比: "根據(jù)老王的線索, 凡是發(fā)現(xiàn)有身高七尺五寸的男性, 都要抓過來審問" 這一整個動作可以被認(rèn)為是一個 aspect.
或則我們也可以從語法的角度來簡單類比一下. 我們在學(xué)英語時, 經(jīng)常會接觸什么 定語, 被動句 之類的概念, 那么可以做一個不嚴(yán)謹(jǐn)?shù)念惐? 即 joinpoint 可以認(rèn)為是一個 賓語, 而 pointcut 則可以類比為修飾 joinpoint 的定語, 那么整個 aspect 就可以描述為: 滿足 pointcut 規(guī)則的 joinpoint 會被添加相應(yīng)的 advice 操作.
@AspectJ 支持@AspectJ 是一種使用 Java 注解來實現(xiàn) AOP 的編碼風(fēng)格.
@AspectJ 風(fēng)格的 AOP 是 AspectJ Project 在 AspectJ 5 中引入的, 并且 Spring 也支持@AspectJ 的 AOP 風(fēng)格.
@AspectJ 可以以 XML 的方式或以注解的方式來使能, 并且不論以哪種方式使能@ASpectJ, 我們都必須保證 aspectjweaver.jar 在 classpath 中.
使用 Java Configuration 方式使能@AspectJ@Configuration @EnableAspectJAutoProxy public class AppConfig { }使用 XML 方式使能@AspectJ
定義 aspect(切面)
當(dāng)使用注解 @Aspect 標(biāo)注一個 Bean 后, 那么 Spring 框架會自動收集這些 Bean, 并添加到 Spring AOP 中, 例如:
@Component @Aspect public class MyTest { }
注意, 僅僅使用@Aspect 注解, 并不能將一個 Java 對象轉(zhuǎn)換為 Bean, 因此我們還需要使用類似 @Component 之類的注解.
注意, 如果一個 類被@Aspect 標(biāo)注, 則這個類就不能是其他 aspect 的 **advised object** 了, 因為使用 @Aspect 后, 這個類就會被排除在 auto-proxying 機制之外.
一個 pointcut 的聲明由兩部分組成:
一個方法簽名, 包括方法名和相關(guān)參數(shù)
一個 pointcut 表達式, 用來指定哪些方法執(zhí)行是我們感興趣的(即因此可以織入 advice).
在@AspectJ 風(fēng)格的 AOP 中, 我們使用一個方法來描述 pointcut, 即:
@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切點表達式 private void dataAccessOperation() {} // 切點前面
這個方法必須無返回值.
這個方法本身就是 pointcut signature, pointcut 表達式使用@Pointcut 注解指定.
上面我們簡單地定義了一個 pointcut, 這個 pointcut 所描述的是: 匹配所有在包 com.xys.service.UserService 下的所有方法的執(zhí)行.
AspectJ5 的切點表達式由標(biāo)志符(designator)和操作參數(shù)組成. 如 "execution( greetTo(..))" 的切點表達式, execution 就是 標(biāo)志符, 而圓括號里的 greetTo(..) 就是操作參數(shù)
匹配 join point 的執(zhí)行, 例如 "execution(* hello(..))" 表示匹配所有目標(biāo)類中的 hello() 方法. 這個是最基本的 pointcut 標(biāo)志符.
匹配特定包下的所有 join point, 例如 within(com.xys.*) 表示 com.xys 包中的所有連接點, 即包中的所有類的所有方法. 而 within(com.xys.service.*Service) 表示在 com.xys.service 包中所有以 Service 結(jié)尾的類的所有的連接點.
this 的作用是匹配一個 bean, 這個 bean(Spring AOP proxy) 是一個給定類型的實例(instance of). 而 target 匹配的是一個目標(biāo)對象(target object, 即需要織入 advice 的原始的類), 此對象是一個給定類型的實例(instance of).
匹配 bean 名字為指定值的 bean 下的所有方法, 例如:
bean(*Service) // 匹配名字后綴為 Service 的 bean 下的所有方法 bean(myService) // 匹配名字為 myService 的 bean 下的所有方法
匹配參數(shù)滿足要求的的方法.
例如:
@Pointcut("within(com.xys.demo2.*)") public void pointcut2() { } @Before(value = "pointcut2() && args(name)") public void doSomething(String name) { logger.info("---page: {}---", name); }
@Service public class NormalService { private Logger logger = LoggerFactory.getLogger(getClass()); public void someMethod() { logger.info("---NormalService: someMethod invoked---"); } public String test(String name) { logger.info("---NormalService: test invoked---"); return "服務(wù)一切正常"; } }
當(dāng) NormalService.test 執(zhí)行時, 則 advice doSomething 就會執(zhí)行, test 方法的參數(shù) name 就會傳遞到 doSomething 中.
常用例子:
// 匹配只有一個參數(shù) name 的方法 @Before(value = "aspectMethod() && args(name)") public void doSomething(String name) { } // 匹配第一個參數(shù)為 name 的方法 @Before(value = "aspectMethod() && args(name, ..)") public void doSomething(String name) { } // 匹配第二個參數(shù)為 name 的方法 Before(value = "aspectMethod() && args(*, name, ..)") public void doSomething(String name) { }
匹配由指定注解所標(biāo)注的方法, 例如:
@Pointcut("@annotation(com.xys.demo1.AuthChecker)") public void pointcut() { }
則匹配由注解 AuthChecker 所標(biāo)注的方法.
常見的切點表達式// 匹配指定包中的所有的方法 execution(* com.xys.service.*(..)) // 匹配當(dāng)前包中的指定類的所有方法 execution(* UserService.*(..)) // 匹配指定包中的所有 public 方法 execution(public * com.xys.service.*(..)) // 匹配指定包中的所有 public 方法, 并且返回值是 int 類型的方法 execution(public int com.xys.service.*(..)) // 匹配指定包中的所有 public 方法, 并且第一個參數(shù)是 String, 返回值是 int 類型的方法 execution(public int com.xys.service.*(String name, ..))
// 匹配指定包中的所有的方法, 但不包括子包 within(com.xys.service.*) // 匹配指定包中的所有的方法, 包括子包 within(com.xys.service..*) // 匹配當(dāng)前包中的指定類中的方法 within(UserService) // 匹配一個接口的所有實現(xiàn)類中的實現(xiàn)的方法 within(UserDao+)
// 匹配以指定名字結(jié)尾的 Bean 中的所有方法 bean(*Service)
// 匹配以 Service 或 ServiceImpl 結(jié)尾的 bean bean(*Service || *ServiceImpl) // 匹配名字以 Service 結(jié)尾, 并且在包 com.xys.service 中的 bean bean(*Service) && within(com.xys.service.*)聲明 advice
advice 是和一個 pointcut 表達式關(guān)聯(lián)在一起的, 并且會在匹配的 join point 的方法執(zhí)行的前/后/周圍 運行. pointcut 表達式可以是簡單的一個 pointcut 名字的引用, 或者是完整的 pointcut 表達式.
下面我們以幾個簡單的 advice 為例子, 來看一下一個 advice 是如何聲明的.
/** * @author xiongyongshun * @version 1.0 * @created 16/9/9 13:13 */ @Component @Aspect public class BeforeAspectTest { // 定義一個 Pointcut, 使用 切點表達式函數(shù) 來描述對哪些 Join point 使用 advise. @Pointcut("execution(* com.xys.service.UserService.*(..))") public void dataAccessOperation() { } }
@Component @Aspect public class AdviseDefine { // 定義 advise @Before("com.xys.aspect.PointcutDefine.dataAccessOperation()") public void doBeforeAccessCheck(JoinPoint joinPoint) { System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****"); } }
這里, @Before 引用了一個 pointcut, 即 "com.xys.aspect.PointcutDefine.dataAccessOperation()" 是一個 pointcut 的名字.
如果我們在 advice 在內(nèi)置 pointcut, 則可以:
@Component @Aspect public class AdviseDefine { // 將 pointcut 和 advice 同時定義 @Before("within(com.xys.service..*)") public void doAccessCheck(JoinPoint joinPoint) { System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****"); } }around advice
around advice 比較特別, 它可以在一個方法的之前之前和之后添加不同的操作, 并且甚至可以決定何時, 如何, 是否調(diào)用匹配到的方法.
@Component @Aspect public class AdviseDefine { // 定義 advise @Around("com.xys.aspect.PointcutDefine.dataAccessOperation()") public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 開始 Object retVal = pjp.proceed(); stopWatch.stop(); // 結(jié)束 System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis()); return retVal; } }
around advice 和前面的 before advice 差不多, 只是我們把注解 @Before 改為了 @Around 了.
下一小節(jié)徹底征服 Spring AOP 之 實戰(zhàn)篇
本文由 yongshun 發(fā)表于個人博客, 采用 署名-相同方式共享 3.0 中國大陸許可協(xié)議.
Email: yongshun1228@gmail .com
本文標(biāo)題為: 徹底征服 Spring AOP 之 實戰(zhàn)篇
本文鏈接為: https://segmentfault.com/a/1190000007469968
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69779.html
摘要:接上一小節(jié)徹底征服之理論篇實戰(zhàn)看了上面這么多的理論知識不知道大家有沒有覺得枯燥哈不過不要急俗話說理論是實踐的基礎(chǔ)對有了基本的理論認(rèn)識后我們來看一下下面幾個具體的例子吧下面的幾個例子是我在工作中所遇見的比較常用的的使用場景我精簡了很多有干擾我 接上一小節(jié)徹底征服 Spring AOP 之 理論篇 Spring AOP 實戰(zhàn) 看了上面這么多的理論知識, 不知道大家有沒有覺得枯燥哈. 不過不...
摘要:簡單點說也就是當(dāng)前切面將會攔截哪些類下的哪些方法,攔截過程中會采用哪些增強處理前置通知,返回通知,異常通知。切面鏈,是一系列的切面的集合。 AOP 術(shù)語 關(guān)于 AOP 的概念描述及相關(guān)術(shù)語可以參考 徹底征服 Spring AOP 之 理論篇 總結(jié)的很好; 本文將著重分析下 AOP 的實現(xiàn)過程。 使用示例 定義接口 public interface UserService { v...
摘要:入門篇學(xué)習(xí)總結(jié)時間年月日星期三說明本文部分內(nèi)容均來自慕課網(wǎng)。主要的功能是日志記錄,性能統(tǒng)計,安全控制,事務(wù)處理,異常處理等等。 《Spring入門篇》學(xué)習(xí)總結(jié) 時間:2017年1月18日星期三說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)示例源碼:https://github.com/zccodere/s...個人學(xué)習(xí)源碼:https://git...
摘要:通知和切點共同定義了關(guān)于切面的全部內(nèi)容,它是什么時候,在何時和何處完成功能引入允許我們向現(xiàn)有的類添加新的方法或者屬性組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時完成例如使用編譯器,也可以在運行時完成。和其他純框架一樣,在運行時完成織入。 原文:190301-SpringBoot基礎(chǔ)篇AOP之基本使用姿勢小結(jié) 一般來講,談到Spring的特性,繞不過去的就是DI(依賴注入)和AOP(切...
摘要:本文會以引出問題為主,后面有時間的話,筆者陸續(xù)會抽些重要的知識點進行詳細(xì)的剖析與解答。敬請關(guān)注服務(wù)端思維微信公眾號,獲取最新文章。 原文地址:梁桂釗的博客博客地址:http://blog.720ui.com 這里,筆者結(jié)合自己過往的面試經(jīng)驗,整理了一些核心的知識清單,幫助讀者更好地回顧與復(fù)習(xí) Java 服務(wù)端核心技術(shù)。本文會以引出問題為主,后面有時間的話,筆者陸續(xù)會抽些重要的知識點進...
閱讀 2312·2021-09-26 10:21
閱讀 2785·2021-09-08 09:36
閱讀 3065·2019-08-30 15:56
閱讀 954·2019-08-30 12:57
閱讀 916·2019-08-26 10:39
閱讀 3555·2019-08-23 18:11
閱讀 3077·2019-08-23 17:12
閱讀 1070·2019-08-23 12:18