AOP(Aspect Orient Programming),做爲面向對象編程的一種補充,普遍應用於處理一些具備橫切性質的系統級服務,如日誌收集、事務管理、安全檢查、緩存、對象池管理等。AOP實現的關鍵就在於AOP框架自動建立的AOP代理,AOP代理則可分爲靜態代理和動態代理兩大類,其中靜態代理是指使用AOP框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,所以也稱爲編譯時加強;而動態代理則在運行時藉助於JDK動態代理
、CGLIB
等在內存中「臨時」生成AOP動態代理類,所以也被稱爲運行時加強。php
面向切面的編程(AOP) 是一種編程範式,旨在經過容許橫切關注點的分離,提升模塊化。AOP提供切面來將跨越對象關注點模塊化。雖然如今能夠得到許多AOP框架,但在這裏咱們要區分的只有兩個流行的框架:Spring AOP和AspectJ。html
Aspect被翻譯方面或者切面,至關於OOP中的類,就是封裝用於橫插入系統的功能。例如日誌、事務、安全驗證等。java
JoinPoint(鏈接點)是AOP中的一個重要的關鍵概念。JoinPoint能夠看作是程序運行時的一個執行點。打個比方,好比執行System.out.println("Hello")這個函數,println()就是一個joinpoint;再如給一個變量賦值也是一個joinpoint;還有最經常使用的for循環,也是一個joinpoint。ios
理論上說,一個程序中不少地方均可以被看作是JoinPoint,可是AspectJ中,只有下面所示的幾種執行點被認爲是JoinPoint:spring
<center>表1 JoinPoint的類型</center>express
JoinPoint | 說明 | 示例 |
---|---|---|
method call | 函數調用 | 好比調用Logger.info(),這是一處JoinPoint |
method execution | 函數執行 | 好比Logger.info()的執行內部,是一處JoinPoint。注意它和method call的區別。method call是調用某個函數的地方。而execution是某個函數執行的內部。 |
constructor call | 構造函數調用 | 和method call相似 |
constructor execution | 構造函數執行 | 和method execution相似 |
field get | 獲取某個變量 | 好比讀取User.name成員 |
field set | 設置某個變量 | 好比設置User.name成員 |
pre-initialization | Object在構造函數中作得一些工做。 | |
initialization | Object在構造函數中作得工做 | |
static initialization | 類初始化 | 好比類的static{} |
handler | 異常處理 | 好比try catch(xxx)中,對應catch內的執行 |
advice execution | 這個是AspectJ的內容 |
這裏列出了AspectJ所承認的JoinPoint的類型。實際上,鏈接點也就是你想把新的代碼插在程序的哪一個地方,是插在構造方法中,仍是插在某個方法調用前,或者是插在某個方法中,這個地方就是JoinPoint,固然,不是全部地方都能給你插的,只有能插的地方,才叫JoinPoint。apache
PointCut通俗地翻譯爲切入點,一個程序會有多個Join Point,即便同一個函數,也還分爲call和execution類型的Join Point,但並非全部的Join Point都是咱們關心的,Pointcut就是提供一種使得開發者可以選擇本身須要的JoinPoint的方法。PointCut分爲call
、execution
、target
、this
、within
等關鍵字。與joinPoint相比,pointcut就是一個具體的切點。編程
Advice翻譯爲通知或者加強(Advisor),就是咱們插入的代碼以何種方式插入,至關於OOP中的方法,有Before、After以及Around。vim
前置通知用於將切面代碼插入方法以前,也就是說,在方法執行以前,會首先執行前置通知裏的代碼.包含前置通知代碼的類就是切面。api
後置通知的代碼在調用被攔截的方法後調用。
環繞通知能力最強,能夠在方法調用前執行通知代碼,能夠決定是否還調用目標方法。也就是說它能夠控制被攔截的方法的執行,還能夠控制被攔截方法的返回值。
Target指的是須要切入的目標類或者目標接口。
Proxy是代理,AOP工做時是經過代理對象來訪問目標對象。其實AOP的實現是經過動態代理,離不開代理模式,因此必需要有一個代理對象。
Weaving即織入,在目標對象中插入切面代碼的過程就叫作織入。
AspectJ是一個面向切面的框架,他定義了AOP的一些語法,有一個專門的字節碼生成器來生成遵照java規範的 class文件。
AspectJ的通知類型不只包括咱們以前瞭解過的三種通知:前置通知、後置通知、環繞通知,在Aspect中還有異常通知以及一種最終通知即不管程序是否正常執行,最終通知的代碼會獲得執行。
AspectJ提供了一套本身的表達式語言即切點表達式,切入點表達式能夠標識切面織入到哪些類的哪些方法當中。只要把切面的實現配置好,再把這個切入點表達式寫好就能夠了,不須要一些額外的xml配置。
切點表達式語法:
execution( modifiers-pattern? //訪問權限匹配 如public、protected ret-type-pattern //返回值類型匹配 declaring-type-pattern? //全限定性類名 name-pattern(param-pattern) //方法名(參數名) throws-pattern? //拋出異常類型 )
注意:
1. 中間以空格隔開,有問號的屬性表示能夠省略。
2. 表達式中特殊符號說明:
*
表明0到多個任意字符,一般用做某個包下面的某些類以及某些方法。..
放在方法參數中,表明任意個參數,放在包名後面表示當前包及其全部子包路徑。+
放在類名後,表示當前類及其子類,放在接口後,表示當前接口及其實現類。<center>表2 方法表達式</center>
表達式 | 含義 |
---|---|
java.lang.String | 匹配String類型 |
java.*.String | 匹配java包下的任何「一級子包」下的String類型,如匹配java.lang.String,但不匹配java.lang.ss.String |
java..* | 匹配java包及任何子包下的任何類型,如匹配java.lang.String、java.lang.annotation.Annotation |
java.lang.*ing | 匹配任何java.lang包下的以ing結尾的類型 |
java.lang.Number+ | 匹配java.lang包下的任何Number的自類型,如匹配java.lang.Integer,也匹配java.math.BigInteger |
<center>表3 參數表達式</center>
參數 | 含義 |
---|---|
() | 表示方法沒有任何參數 |
(..) | 表示匹配接受任意個參數的方法 |
(..,java.lang.String) | 表示匹配接受java.lang.String類型的參數結束,且其前邊能夠接受有任意個參數的方法 |
(java.lang.String,..) | 表示匹配接受java.lang.String類型的參數開始,且其後邊能夠接受任意個參數的方法 |
(*,java.lang.String) | 表示匹配接受java.lang.String類型的參數結束,且其前邊接受有一個任意類型參數的方法 |
舉個栗子:execution(public * com.zhoujunwen.service.*.*(..))
,該表達式表示com.zhoujunwen.service包下的public訪問權限的任意類的任意方法。
AspectJ下載地址(http://www.eclipse.org/aspect...,在下載頁面選擇合適的版本下載,目前最新穩定版是1.9.1。下載完以後雙加jar包安裝,安裝界面以下:
安裝目錄用tree命令能夠看到以下結構(省去doc目錄):
├── LICENSE-AspectJ.html ├── README-AspectJ.html ├── bin │ ├── aj │ ├── aj5 │ ├── ajbrowser │ ├── ajc │ └── ajdoc └── lib ├── aspectjrt.jar ├── aspectjtools.jar ├── aspectjweaver.jar └── org.aspectj.matcher.jar 42 directories, 440 files
注意安裝完成後,須要配置將aspectjrt.jar
配置到CLASSPATH中,而且將bin
目錄配置到PATH中。下面以MacOs配置爲例:
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:/Users/yourname/Documents/software/aspectj1.9.1/lib/aspectjrt.jar M2_HOME=/Users/yourname/Documents/software/apache-maven-3.5.0 PATH=$JAVA_HOME/bin:$M2_HOME/bin:/usr/local/bin:/Users/yourname/Documents/software/aspectj1.9.1/bin:$PATH
注意:其中/Users/yourname/Documents/software/aspectj1.9.1/lib/aspectjrt.jar
替換爲本身安裝AspectJ的路徑的lib,/Users/yourname/Documents/software/aspectj1.9.1/bin
替換爲安裝AspectJ的bin目錄
驗證AspectJ的切面功能,寫個單純的AspectJ的demo,實現方法日誌埋點,在方法後加強。
業務代碼(AuthorizeService.java):
package com.zhoujunwen.aop; /** * 不用太過於較真業務邏輯的處理,大概意思你們懂就好。 * @author zhoujunwen * @version 1.0.0 */ public class AuthorizeService { private static final String USERNAME = "zhoujunwen"; private static final String PASSWORD = "123456"; public void login(String username, String password) { if (username == null || username.length() == 0) { System.out.print("用戶名不能爲空"); return; } if (password == null || password.length() == 0) { System.out.print("用戶名不能爲空"); return; } if (!USERNAME.equals(username) || !PASSWORD.equals(password)) { System.out.print("用戶名或者密碼不對"); return; } System.out.print("登陸成功"); } public static void main(String[] args) { AuthorizeService as = new AuthorizeService(); as.login("zhoujunwen", "123456"); } }
日誌埋點切面邏輯(LogAspect.java):
package com.zhoujunwen.aop; public aspect LogAspect { pointcut logPointcut():execution(void AuthorizeService.login(..)); after():logPointcut(){ System.out.println("****處理日誌****"); } }
將上述兩個文件文件放置在同一個目錄,在當前目錄下執行acj編譯和織入命令:
ajc -d . AuthorizeService.java LogAspect.java
若是配置一切OK的話,不會出現異常或者錯誤,並在當前目錄生成com/zhoujunwen/aop/AuthorizeService.class
和com/zhoujunwen/aop/LogAspect.class
兩個字節碼文件,執行tree(本身編寫的相似Linux的tree命令)命令查看目錄結構:
zhoujunwendeMacBook-Air:aop zhoujunwen$ tree . ├── AuthorizeService.java ├── LogAspect.java └── com └── zhoujunwen └── aop ├── AuthorizeService.class └── LogAspect.class 3 directories, 4 files
最後執行java執行命令:
java com/zhoujunwen/aop/AuthorizeService
輸出日誌內容:
登陸成功處理日誌
ajc能夠理解爲javac命令,都用於編譯Java程序,區別是ajc命令可識別AspectJ的語法;咱們能夠將ajc當成一個加強版的javac命令。執行ajc命令後的AuthorizeService.class 文件不是由原來的AuthorizeService.java文件編譯獲得的,該AuthorizeService.class裏新增了打印日誌的內容——這代表AspectJ在編譯時「自動」編譯獲得了一個新類,這個新類加強了原有的AuthorizeService.java類的功能,所以AspectJ一般被稱爲編譯時加強的AOP框架。
爲了驗證上述的結論,咱們用javap命令反編譯AuthorizeService.class文件。javap是Java class文件分解器,能夠反編譯(即對javac編譯的文件進行反編譯),也能夠查看java編譯器生成的字節碼。用於分解class文件。
javap -p -c com/zhoujunwen/aop/AuthorizeService.class
輸出內容以下,在login方法的code爲0、3以及9一、94的地方,會發現invokestatic
和com/zhoujunwen/aop/LogAspect
的代碼,這說明上面的結論是正確的。
Compiled from "AuthorizeService.java" public class com.zhoujunwen.aop.AuthorizeService { private static final java.lang.String USERNAME; private static final java.lang.String PASSWORD; public com.zhoujunwen.aop.AuthorizeService(); Code: 0: aload_0 1: invokespecial #16 // Method java/lang/Object."<init>":()V 4: return public void login(java.lang.String, java.lang.String); Code: 0: invokestatic #70 // Method com/zhoujunwen/aop/LogAspect.aspectOf:()Lcom/zhoujunwen/aop/LogAspect; 3: invokevirtual #76 // Method com/zhoujunwen/aop/LogAspect.ajc$before$com_zhoujunwen_aop_LogAspect$2$9fd5dd97:()V 6: aload_1 7: ifnull 17 10: aload_1 11: invokevirtual #25 // Method java/lang/String.length:()I 14: ifne 28 17: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream; 20: ldc #37 // String 用戶名不能爲空 22: invokevirtual #39 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 25: goto 99 28: aload_2 29: ifnull 39 32: aload_2 33: invokevirtual #25 // Method java/lang/String.length:()I 36: ifne 50 39: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream; 42: ldc #37 // String 用戶名不能爲空 44: invokevirtual #39 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 47: goto 99 50: ldc #8 // String zhoujunwen 52: aload_1 53: invokevirtual #45 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 56: ifeq 68 59: ldc #11 // String 123456 61: aload_2 62: invokevirtual #45 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 65: ifne 79 68: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream; 71: ldc #49 // String 用戶名或者密碼不對 73: invokevirtual #39 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 76: goto 99 79: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream; 82: ldc #51 // String 登陸成功 84: invokevirtual #39 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 87: goto 99 90: astore_3 91: invokestatic #70 // Method com/zhoujunwen/aop/LogAspect.aspectOf:()Lcom/zhoujunwen/aop/LogAspect; 94: invokevirtual #73 // Method com/zhoujunwen/aop/LogAspect.ajc$after$com_zhoujunwen_aop_LogAspect$1$9fd5dd97:()V 97: aload_3 98: athrow 99: invokestatic #70 // Method com/zhoujunwen/aop/LogAspect.aspectOf:()Lcom/zhoujunwen/aop/LogAspect; 102: invokevirtual #73 // Method com/zhoujunwen/aop/LogAspect.ajc$after$com_zhoujunwen_aop_LogAspect$1$9fd5dd97:()V 105: return Exception table: from to target type 6 90 90 Class java/lang/Throwable public static void main(java.lang.String[]); Code: 0: new #1 // class com/zhoujunwen/aop/AuthorizeService 3: dup 4: invokespecial #57 // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #8 // String zhoujunwen 11: ldc #11 // String 123456 13: invokevirtual #58 // Method login:(Ljava/lang/String;Ljava/lang/String;)V 16: return }
Spring AOP也是對目標類加強,生成代理類。可是與AspectJ的最大區別在於——Spring AOP的運行時加強,而AspectJ是編譯時加強。
dolphin叔叔文章中寫道本身曾經誤覺得AspectJ是Spring AOP的一部分,我想大多數人都沒有弄清楚AspectJ和Spring AOP的關係。
當你不用Spring AOP提供的註解時,Spring AOP和AspectJ沒半毛錢的關係,前者是JDK動態代理,用到了CGLIB(Code Generation Library),CGLIB是一個代碼生成類庫,能夠在運行時候動態是生成某個類的子類。代理模式爲要訪問的目標對象提供了一種途徑,當訪問對象時,它引入了一個間接的層。後者是靜態代理,在編譯階段就已經編譯到字節碼文件中。Spring中提供了前置通知org.springframework.aop.MethodBeforeAdvice
、後置通知org.springframework.aop.AfterReturningAdvice
,環繞通知org.aopalliance.intercept.MethodInvocation
(經過反射實現,invoke(org.aopalliance.intercept.MethodInvocation mi)中的MethodInvocation獲取目標方法,目標類,目標字段等信息),異常通知org.springframework.aop.ThrowsAdvice
。這些通知可以切入目標對象,Spring AOP的核心是代理Proxy,其主要實現類是org.springframework.aop.framework.ProxyFactoryBean
,ProxyFactoryBean中proxyInterfaces
爲代理指向的目標接口,Spring AOP沒法截獲未在該屬性指定的接口中的方法,interceptorNames
是攔截列表,target
是目標接口實現類,一個代理只能有一個target。
Spring AOP的核心類org.springframework.aop.framework.ProxyFactoryBean
雖然能實現AOP的行爲,可是這種方式具備侷限性,須要在代碼中顯式的調用ProxyFactoryBean代理工廠類,舉例:UserService是一個接口,UserServiceImpl是UserService的實現類,ApplicationContext context爲Spring上下文,調用方式爲UserService userService = (UserService)context.getBean("userProxy");
。
完整的配置以下:
<bean id="userService" class="com.zhoujunwen.UserServiceImpl"></bean> <!-- 定義前置通知,com.zhoujunwen.BeforeLogAdvice實現了org.springframework.aop.MethodBeforeAdvice --> <bean id="beforeLogAdvice" class="com.zhoujunwen.BeforeLogAdvice"></bean> <!-- 定義後置通知,com.zhoujunwen.AfterLogAdvice實現了org.springframework.aop.AfterReturningAdvice --> <bean id="afterLogAdvice" class="com.zhoujunwen.AfterLogAdvice"></bean> <!-- 定義異常通知, com.zhoujunwen.ThrowsLogAdvice實現了org.springframework.aop.ThrowsAdvice--> <bean id="throwsLogAdvice" class="com.zhoujunwen.ThrowsLogAdvice"></bean> <!-- 定義環繞通知,com.zhoujunwen.LogAroundAdvice實現了org.aopalliance.intercept.MethodInvocation --> <bean id="logAroundAdvice" class="com.zhoujunwen.LogAroundAdvice"></bean> <!-- 定義代理類,名 稱爲userProxy,將經過userProxy訪問業務類中的方法 --> <bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>com.zhoujunwen.UserService</value> </property> <property name="interceptorNames"> <list> <value>beforeLogAdvice</value> <!-- 織入後置通知 --> <value>afterLogAdvice</value> <!-- 織入異常通知 --> <value>throwsLogAdvice</value> <!-- 織入環繞通知 --> <value>logAroundAdvice</value> </list> </property> <property name="target" ref="userService"></property> </bean>
固然,上述的侷限性spring官方也給出瞭解決方案,讓AOP的通知在服務調用方絕不知情的下就進行織入,能夠經過org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator
自動代理。
<bean id="myServiceAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <value>logAroundAdvice</value> </list> </property> <property name="beanNames"> <value>*Service</value> </property> </bean>
這個BeanNameAutoProxyCreator的bean中指明上下文中全部調用以Service結尾的服務類都會被攔截,執行logAroundAdvice的invoke方法。同時它會自動生成Service的代理,這樣在使用的時候就能夠直接取服務類的bean,而不用再像上面那樣還用取代理類的bean。
對於BeanNameAutoProxyCreator建立的代理,能夠這樣調用:UserService userService = (UserService) context.getBean("userService");
,context爲spring上下文。
當你用到Spring AOP提供的注入@Before、@After等註解時,Spring AOP和AspectJ就有了關係。在開發中引入了org.aspectj:aspectjrt:1.6.11
和org.aspectj:aspectjweaver:1.6.11
兩個包,這是由於Spring AOP使用了AspectJ的Annotation,使用了Aspect來定義切面,使用Pointcut來定義切入點,使用Advice來定義加強處理。雖然Spring AOP使用了Aspect的Annotation,可是並無使用它的編譯器和織入器。
Spring AOP其實現原理是JDK動態代理,在運行時生成代理類。爲了啓用Spring對@AspectJ
切面配置的支持,並保證Spring容器中的目標Bean被一個或多個切面自動加強,必須在Spring配置文件中添加以下配置
<aop:aspectj-autoproxy/>
當啓動了@AspectJ支持後,在Spring容器中配置一個帶@Aspect
註釋的Bean,Spring將會自動識別該 Bean,並將該Bean做爲切面Bean處理。切面Bean與普通Bean沒有任何區別,同樣使用<bean.../>
元素進行配置,同樣支持使用依賴注入來配置屬性值。
業務邏輯代碼(AuthorizeService.java):
package com.zhoujunwen.engine.service; import org.springframework.stereotype.Service; /** * Created with IntelliJ IDEA. * Date: 2018/10/25 * Time: 12:47 PM * Description: * * @author zhoujunwen * @version 1.0 */ @Service public class AuthorizeService { private static final String USERNAME = "zhoujunwen"; private static final String PASSWORD = "123456"; public void login(String username, String password) { if (username == null || username.length() == 0) { System.out.print("用戶名不能爲空"); return; } if (password == null || password.length() == 0) { System.out.print("用戶名不能爲空"); return; } if (!USERNAME.equals(username) || !PASSWORD.equals(password)) { System.out.print("用戶名或者密碼不對"); return; } System.out.print("登陸成功"); } }
切面邏輯代碼(LogAspect.java)
package com.zhoujunwen.engine.service; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; /** * Created with IntelliJ IDEA. * Date: 2018/10/25 * Time: 1:04 PM * Description: * * @author zhoujunwen * @version 1.0 */ @Aspect @Component public class LogAspect { @After("execution(* com.zhoujunwen.engine.service.AuthorizeService.login(..))") public void logPointcut(){ System.out.println("***處理日誌***"); } }
這樣是實現了對AuthorizeService.login()方法的後置通知。不須要在xml中其餘配置,固然前提是開啓<aop:aspectj-autoproxy/>
aspectj的自動代理。
測試調用代碼:
AuthorizeService authorizeService = SpringContextHolder.getBean(AuthorizeService.class); authorizeService.login("zhangsan", "zs2018");
業務代碼,日誌埋點(MeasurementService.java):
package com.zhoujunwen.engine.measurement; import com.zhoujunwen.common.base.AccountInfo; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; /** * metrics 切面接口 * @create 2018-08-16-上午10:13 */ @Service public class MeasurementService { private static final Logger LOGGER = LoggerFactory.getLogger(MeasurementService.class); public String gainZhimaLog(AccountInfo accountInfo) { if (NumberUtils.isNumber(accountInfo.getZhimaPoint())) { return "正常"; } else if (StringUtils.contains(accountInfo.getZhimaPoint(), "*")) { return "未受權"; } else { return "未爬到"; } } public String gainJiebeiLog(AccountInfo accountInfo) { if (NumberUtils.isNumber(accountInfo.getJiebeiQuota())) { return "正常"; } return "未爬到"; } public String gainHuabeiLog(AccountInfo accountInfo) { if (accountInfo.getCreditQuota() != null) { return "正常"; } else { return "未爬到"; } } }
切面邏輯,統計日誌中個字段的總和(KeywordMeasurement.java):
package com.zhoujunwen.engine.measurement; import com.zhoujunwen.common.base.AccountInfo; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; /** * 關鍵字段監控統計 <br> * * @create 2018-08-15-下午5:41 */ public class KeywordMeasurement { private String invokeCountFieldName = ""; /** * 調用次數 */ public void summary(JoinPoint joinPoint, Object result) { try { String msg; String resultStr = ""; if (result instanceof String) { resultStr = (String) result; } if (StringUtils.isBlank(resultStr)) { return; } if ("正常".equals(resultStr)) { msg = "_ok"; } else if ("未爬到".equals(resultStr)) { msg = "_empty"; } else { msg = "_star"; } String methodName = joinPoint.getSignature().getName(); Object args[] = joinPoint.getArgs(); AccountInfo accountInfo = null; for (Object arg : args) { if (arg.getClass().getName().contains("AccountInfo")) { accountInfo = (accountInfo) arg; } } if (methodName.contains("Zhima")) { invokeCountFieldName = "zhima" + msg; } else if (methodName.contains("Jiebei")) { invokeCountFieldName = "jiebei" + msg; } else if (methodName.contains("Huabei")) { invokeCountFieldName = "huabei" + msg; } else { return; } // TODO 寫入到influxDB } catch (Exception e) { //skip } } }
完整的配置(後置通知,並須要返回結果):
<bean id="keywordMeasurement" class="com.zhoujunwen.engine.measurement.KeywordMeasurement"/> <aop:config proxy-target-class="true"> <aop:aspect id="keywordMeasurementAspect" ref="keywordMeasurement"> <aop:pointcut id="keywordMeasurementPointcut" expression="execution(* com.zhoujunwen.engine.measurement.SdkMeasurementService.gain*(..))"/> <!-- 統計summary,summary方法有兩個參數JoinPoint和Object--> <aop:after-returning method="summary" returning="result" pointcut-ref="keywordMeasurementPointcut"/> </aop:aspect> </aop:config>
其餘可用的配置(省略了rt、count、qps的aspect):
<!-- 統計RT,rt方法只有一個參數ProceedingJoinPoint--> <aop:around method="rt" pointcut-ref="keywordMeasurementPointcut"/> <!--統計調用次數,count方法只有一個參數JoinPoint--> <aop:after method="count" pointcut-ref="keywordMeasurementPointcut"/> <!--統計QPS,qps方法只有一個參數JoinPoint--> <aop:after method="qps" pointcut-ref="keywordMeasurementPointcut"/>
注意:關於Spring AOP中,切面代理類必定是由Spirng容器管理,因此委託類也須要交由Spring管理,不能夠將委託類實例交由本身建立的容器管理(好比放入本身建立的Map中),若是這麼作了,當調用委託類實例的時候,切面是不生效的。
緣由:(1)實現實現和目標類相同的接口,spring會使用JDK的java.lang.reflect.Proxy類,它容許Spring動態生成一個新類來實現必要的接口,織入通知,而且把這些接口的任何調用都轉發到目標類。
(2)生成子類調用,spring使用CGLIB庫生成目標類的一個子類,在建立這個子類的時候,spring織入通知,而且把對這個子類的調用委託到目標類。
AspectJ和Spring AOP都是對目標類加強,生成代理類。
AspectJ是在編譯期間將切面代碼編譯到目標代碼的,屬於靜態代理;Spring AOP是在運行期間經過代理生成目標類,屬於動態代理。
AspectJ是靜態代理,故而可以切入final修飾的類,abstract修飾的類;Spring AOP是動態代理,其實現原理是經過CGLIB生成一個繼承了目標類(委託類)的代理類,所以,final修飾的類不能被代理,一樣static和final修飾的方法也不會代理,由於static和final方法是不能被覆蓋的。在CGLIB底層,實際上是藉助了ASM這個很是強大的Java字節碼生成框架。關於CGLB和ASM的討論將會新開一個篇幅探討。
Spring AOP支持註解,在使用@Aspect註解建立和配置切面時將更加方便。而使用AspectJ,須要經過.aj文件來建立切面,而且須要使用ajc(Aspect編譯器)來編譯代碼。
首先須要考慮,Spring AOP致力於提供一種可以與Spring IoC緊密集成的面向切面框架的實現,以便於解決在開發企業級項目時面臨的常見問題。明確你在應用橫切關注點(cross-cutting concern)時(例如事物管理、日誌或性能評估),須要處理的是Spring beans仍是POJO。若是正在開發新的應用,則選擇Spring AOP就沒有什麼阻力。可是若是你正在維護一個現有的應用(該應用並無使用Spring框架),AspectJ就將是一個天然的選擇了。爲了詳細說明這一點,假如你正在使用Spring AOP,當你想將日誌功能做爲一個通知(advice)加入到你的應用中,用於追蹤程序流程,那麼該通知(Advice)就只能應用在Spring beans的鏈接點(Joinpoint)之上。
另外一個須要考慮的因素是,你是但願在編譯期間進行織入(weaving),仍是編譯後(post-compile)或是運行時(run-time)。Spring只支持運行時織入。若是你有多個團隊分別開發多個使用Spring編寫的模塊(致使生成多個jar文件,例如每一個模塊一個jar文件),而且其中一個團隊想要在整個項目中的全部Spring bean(例如,包括已經被其餘團隊打包了的jar文件)上應用日誌通知(在這裏日誌只是用於加入橫切關注點的舉例),那麼經過配置該團隊本身的Spring配置文件就能夠輕鬆作到這一點。之因此能夠這樣作,就是由於Spring使用的是運行時織入。
還有一點,由於Spring基於代理模式(使用CGLIB),它有一個使用限制,即沒法在使用final修飾的bean上應用橫切關注點。由於代理須要對Java類進行繼承,一旦使用了關鍵字final,這將是沒法作到的。在這種狀況下,你也許會考慮使用AspectJ,其支持編譯期織入且不須要生成代理。於此類似,在static和final方法上應用橫切關注點也是沒法作到的。由於Spring基於代理模式。若是你在這些方法上配置通知,將致使運行時異常,由於static和final方法是不能被覆蓋的。在這種狀況下,你也會考慮使用AspectJ,由於其支持編譯期織入且不須要生成代理。
若是你但願使用一種易於實現的方式,就選擇Spring AOP吧,由於Spring AOP支持註解,在使用@Aspect註解建立和配置切面時將更加方便。而使用AspectJ,你就須要經過.aj文件來建立切面,而且須要使用ajc(Aspect編譯器)來編譯代碼。因此若是你肯定以前提到的限制不會成爲你的項目的障礙時,使用Spring AOP。AspectJ的一個間接侷限是,由於AspectJ通知能夠應用於POJO之上,它有可能將通知應用於一個已配置的通知之上。對於一個你沒有注意到這切面問題的大範圍應用的通知,這有可能致使一個無限循環。在下面這種狀況下,當proceed即將被調用時,日誌通知會被再次應用,這樣就致使了嵌套循環。
public aspectLogging { Object around() : execution(public * * (..)) Sysytem.out.println(thisJoinPoint.getSignature()); return proceed(); }
誠摯感謝如下文章及做者,也是讓我在參考實踐以及理論總結的過程當中學習到了不少東西。不作無頭無腦的抄襲者,要作閱讀他人的文章,汲取精粹,親自實踐得出結論。尊重原創,尊重做者!
AspectJ(一) 一些該瞭解的概念
AspectJ 框架,比用 spring 實現 AOP 好用不少喲!
比較分析 Spring AOP 和 AspectJ 之間的差異
AspectJ基本用法
應用Spring AOP(一)
AspectJ官方doc文檔
Spring AOP,AspectJ, CGLIB 有點暈
該文首發《虛懷若谷》我的博客,轉載前請務必署名,轉載請標明出處。
古之善爲道者,微妙玄通,深不可識。夫惟不可識,故強爲之容:豫兮若冬涉川,猶兮若畏四鄰,儼兮其若客,渙兮若冰之釋,敦兮其若樸,曠兮其若谷,混兮其若濁。
孰能濁以靜之徐清?孰能安以動之徐生?
保此道不欲盈。夫惟不盈,故能敝而新成。
請關注個人微信公衆號:下雨就像彈鋼琴,Thanks♪(・ω・)ノ