spring 切面配置


最近做的項目出現了某些功能打開太慢的問題,追蹤時一般都是加上日志進行分析,但每個業務都加上日志太麻煩,於是想到了spring切面來進行處理,在切面中加上方法運行時間,然后就可以監控哪些方法運行慢,進而優化。下面記錄下配置信息:

1、首先創建切面類 

LogAspect.java
 
        
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

/**
 * 檢測方法執行耗時的spring切面類
 * Spring將會把它當作一個特殊的Bean(一個切面),
 * 也就是不對這個類本身進行動態代理
 *
 * @author sun
 * @date 2018-9-13
 */
public class LogAspect {
    private static Log logger = LogFactory.getLog(LogAspect.class);
    /**
     * 統計方法執行耗時Around環繞通知
     *  @param joinPoint
     *  @return
     */
    public Object timeAround(ProceedingJoinPoint joinPoint) {
        // 定義返回對象、得到方法需要的參數
        Object obj = null;
        Object[] args = joinPoint.getArgs();
        long startTime = System.currentTimeMillis();
        try {
            obj = joinPoint.proceed(args);
        } catch (Throwable e) {
            logger.error("統計某方法執行耗時環繞通知出錯", e);
        } // 獲取執行的方法名
        long endTime = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
        // 打印耗時的信息
        logger.info("====================>" + methodName + " 方法執行耗時:" + ( endTime - startTime) + " ms");
        return obj;
    }
}

  2、然后在spring配置文件中新增如下配置:

<!--	 激活自動代理功能-->
	<aop:aspectj-autoproxy proxy-target-class="true"/>
	<!-- 系統服務組件的切面Bean -->
	<bean id="aspectService" class="com.tera.sys.filter.LogAspect"></bean>
	<!-- AOP配置 -->
	<aop:config>
		<!-- 聲明一個切面,並注入切面Bean,相當於@Aspect -->
		<aop:aspect id="simpleAspect" ref="aspectService">
			<!-- 配置一個切入點,相當於@Pointcut -->
			<aop:pointcut expression="execution (* com.tera.*..*Service.*(..)) " id="simplePointcut"/>
			<aop:around pointcut-ref="simplePointcut" method="timeAround"/>
		</aop:aspect>
	</aop:config>

  其中 

 execution (* com.tera.*..*Service.*(..))  即切入點為所有的Service 的方法,如需要監控其他的類,改成其他配置即可,
  在多個表達式之間使用 ||,or表示 或,使用 &&,and表示 與,!表示 非。


Aspectj切入點語法定義
 

在使用spring框架配置AOP的時候,不管是通過XML配置文件還是注解的方式都需要定義pointcut"切入點"

例如定義切入點表達式 execution(* com.sample.service.impl..*.*(..))

execution()是最常用的切點函數,其語法如下所示:

 整個表達式可以分為五個部分:

 1、execution(): 表達式主體。

 2、第一個*號:表示返回類型,*號表示所有的類型。

 3、包名:表示需要攔截的包名,后面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。

 4、第二個*號:表示類名,*號表示所有的類。

 5、*(..):最后這個星號表示方法名,*號表示所有的方法,后面括弧里面表示方法的參數,兩個句點表示任何參數。

 

 

 

 

AspectJ的Execution表達式

execution()

execution()是最常用的切點函數,其語法如下所示:

 

execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數模式>)<異常模式>?)  除了返回類型模式、方法名模式和參數模式外,其它項都是可選的。與其直接講解該方法的使用規則,還不如通過一個個具體的例子進行理解。下面,我們給出各種使用execution()函數實例。

 

1)通過方法簽名定義切點

 execution(public * *(..))l

匹配所有目標類的public方法,但不匹配SmartSeller和protected voidshowGoods()方法。第一個*代表返回類型,第二個*代表方法名,而..代表任意入參的方法;

 

 execution(* *To(..))l

匹配目標類所有以To為后綴的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一個*代表返回類型,而*To代表任意以To為后綴的方法;

 

2)通過類定義切點

 execution(*com.baobaotao.Waiter.*(..))l

匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter類的greetTo()和serveTo()方法。第一個*代表返回任意類型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法;

 

 execution(*com.baobaotao.Waiter+.*(..))l

匹配Waiter接口及其所有實現類的方法,它不但匹配NaiveWaiter和NaughtyWaiter類的greetTo()和serveTo()這兩個Waiter接口定義的方法,同時還匹配NaiveWaiter#smile()和NaughtyWaiter#joke()這兩個不在Waiter接口中定義的方法。

 

3)通過類包定義切點

在類名模式串中,“.*”表示包下的所有類,而“..*”表示包、子孫包下的所有類。

 execution(* com.baobaotao.*(..))l

匹配com.baobaotao包下所有類的所有方法;

 

 execution(* com.baobaotao..*(..))l

匹配com.baobaotao包、子孫包下所有類的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有類的所有方法都匹配。“..”出現在類名中時,后面必須跟“*”,表示包、子孫包下的所有類;

 

 execution(* com..*.*Dao.find*(..))l

匹配包名前綴為com的任何包下類名后綴為Dao的方法,方法名必須以find為前綴。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切點。

 

4)通過方法入參定義切點

切點表達式中方法入參部分比較復雜,可以使用“*”和“..”通配符,其中“*”表示任意類型的參數,而“..”表示任意類型參數且參數個數不限。

 

 execution(* joke(String,int)))l

匹 配joke(String,int)方法,且joke()方法的第一個入參是String,第二個入參是int。它匹配NaughtyWaiter#joke(String,int)方法。如果方法中的入參類型是java.lang包下的類,可以直接使用類名,否則必須使用全限定類名,如joke(java.util.List,int);

 

 execution(* joke(String,*)))l

匹 配目標類中的joke()方法,該方法第一個入參為String,第二個入參可以是任意類型,如joke(Strings1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,doubled2,String s3)則不匹配;

 

 execution(* joke(String,..)))l

匹配目標類中的joke()方法,該方法第 一個入參為String,后面可以有任意個入參且入參類型不限,如joke(Strings1)、joke(String s1,String s2)和joke(String s1,double d2,Strings3)都匹配。

 

 execution(* joke(Object+)))l

匹 配目標類中的joke()方法,方法擁有一個入參,且入參是Object類型或該類的子類。它匹配joke(Strings1)和joke(Client c)。如果我們定義的切點是execution(*joke(Object)),則只匹配joke(Object object)而不匹配joke(Stringcc)或joke(Client c)。

 

args()和@args()

args()函數的入參是類名,@args()函數的入參必須是注解類的類名。雖然args()允許在類名后使用+通配符后綴,但該通配符在此處沒有意義:添加和不添加效果都一樣。

 

1)args()

該函數接受一個類名,表示目標類方法入參對象按類型匹配於指定類時,切點匹配,如下面的例子:

args(com.baobaotao.Waiter)

表 示運行時入參是Waiter類型的方法,它和execution(**(com.baobaotao.Waiter))區別在於后者是針對類方法的簽名而言的,而前者則針對運行時的入參類型而言。如args(com.baobaotao.Waiter)既匹配於addWaiter(Waiterwaiter),也匹配於addNaiveWaiter(NaiveWaiter naiveWaiter),而execution(**(com.baobaotao.Waiter))只匹配addWaiter(Waiterwaiter)方法;實際上,args(com.baobaotao.Waiter)等價於execution(**(com.baobaotao.Waiter+)),當然也等價於args(com.baobaotao.Waiter+)。

 

2)@args()

該函數接受一個注解類的類名,當方法的運行時入參對象標注發指定的注解時,方法匹配切點。這個切點函數的匹配規則不太容易理解,我們通過以下示意圖對此進行詳細講解:

 

 

            圖4 @arg(M)匹配示意圖(1)

    T0、T1、T2、T3具有如圖所示的繼承關系,假設目標類方法的簽名為fun(T1t),它的入參為T1,而切面的切點定義為@args(M),T2類標注了@M。當fun(T1t)傳入對象是T2或T3時,則方法匹配@args(M)所聲明定義的切點;

 

再看下面的情況,假設方法簽名是fun(T1 t),入參對於T1,而標注@M的類是T0,當funt(T1t)傳入T1、T2、T3的實例時,均不匹配切點@args(M)。

 

            圖5 @arg(M)匹配示意圖(2)

    在類的繼承樹中,①點為方法簽名中入參類型在類繼承樹中的位置,我們稱之為入參類型點,而②為標注了@M注解的類在類繼承樹中位置,我們稱之為注解點。判斷方法在運行時是否匹配@agrs(M)切點,可以根據①點和②點在類繼承樹中的相對位置來判別:

1) 如果在類繼承樹中注解點②高於入參類型點①,則該目標方法不可能匹配切點@args(M),如圖 5所示;

2) 如果在類繼承樹中注解點②低於入參類型點①,則注解點所在類及其子孫類作為方法入參時,該方法匹配@args(M)切點,如圖4所示。

下 面舉一個具體的例子,假設我們定義這樣的切點:@args(com.baobaotao.Monitorable),如果NaiveWaiter標注了@Monitorable,則對於WaiterManager#addWaiter(Waiterw)方法來說,如果入參是NaiveWaiter或其子類對象,該方法匹配切點,如果入參是NaughtyWaiter對象,不匹配切點。如果Waiter標注了@Monitorable,但NaiveWaiter未標注@Monitorable,則WaiterManager#addNaiveWaiter(NaiveWaiterw)卻不匹配切點,這是因為注解點(在Waiter)高於入參類型點(NaiveWaiter)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM