SpringAOP學習

author:luojie

  1. 1.  AOP中的基本概念

AOP的通用術語,並非spring java所特有。很遺憾AOP的術語不是特別的直觀。但如果讓Spring java來定義自己的專用名詞,可能會更加教人糊塗。 



方面(Aspect):對橫向分佈在多個物件中的關注點所做的模組化。在企業應用中,事務管理就是一個典型的橫切關注點。Spring
java將方面實現為Advisor或攔截器(interceptor)。(按:Advisor是通知和切入點的組合,攔截器實際就是指通知,注意在本文件中,一般會把環繞通知稱為攔截器,而將其它型別的通知稱為通知,這是因為環繞通知實現的是AopAlliance.Intercept.IMethodInterceptor介面,而其它通知型別實現的都是Spring.Aop名稱空間下的通知介面。) 



連線點(Joinpoint):程式執行過程中的一個點,例如對某個方法的呼叫或者某個特定異常的丟擲都可以稱為連線點。 



通知(Advice):AOP框架在某個連線點所採取的行為。通知有多種型別,包括“環繞”通知,“前置”通知和“異常”通知等,後文將對通知型別進行討論。包括Spring.NET在內的很多AOP框架都把通知建模為攔截器(interceptor),並且會維護一個"包圍"在連線點周圍的攔截器鏈。 



切入點(Pointcut):指通知的應用條件,用於確定某個通知要被應用到哪些連線點上。AOP框架應允許讓開發人員指定切入點,例如,可以使用正則表示式來指定一個切入點。 



引入(Introduction):向目標物件新增方法或欄位的行為。Spring.NET允許為任何目標物件引入新的介面。例如,可以利用引入讓任何物件在執行期實現IAuditable介面,以簡化物件狀態變化的跟蹤過程。(按:也稱為mixin,混入) 



目標物件(Target object):指包含連線點的物件。也稱為被通知或被代理物件。(按:“被通知物件”實際是“被應用了通知的物件”,在譯文中,將advised object或proxied object統稱為目標物件,這樣更為統一) 



AOP代理(AOP
proxy):由AOP框架在將通知應用於目標物件後建立的物件。在Spring.NET中,AOP代理是使用IL程式碼在執行時建立的動態代理。 



織入(Weaving):將方面進行組裝,以建立一個目標物件。織入可以在編譯期完成(例如使用Gripper_Loom.NET編譯器),也可以在執行時完成。Spring.NET在執行時執行織入。 







各種通知型別包括: 



環繞通知(Around Advise):包圍(按:即在連線點執行的前、後執行)某個連線點(如方法呼叫)的通知。這是功能最強大的一種通知。環繞通知允許在方法呼叫的前後執行自定義行為。它可以決定是讓連線點繼續執行,還是用自己的返回值或異常來將連線點“短路”。 



前置通知(Before Advise):在某個連線點執行之前執行,但是不具備阻止連線點繼續執行的能力(除非它丟擲異常)。 



異常通知(Throws Advise):當方法(連線點)丟擲異常時執行。Spring.NET的異常通知是強型別的(按:Spring.NET用標識介面來定義異常通知,異常通知的處理方法僅需遵循一定的命名規則,可以用具體的異常型別宣告其引數,參見12.3.2.3節),所以,可以在程式碼中直接捕捉某個型別的異常(及其子類異常),不必從Exception轉型。 



後置通知(After returning Advise):在連線點正常執行完成後執行,例如,如果方法正常返回,沒有丟擲異常時,後置通知就會被執行。 



Spring java內建了以上所有型別的通知。在應用時,應儘量使用功能最少(只要對要實現的行為來說是足夠的)的通知型別,這樣可簡化程式設計模型並減少出錯的可能。例如,如果只需使用某個方法的返回值來更新快取,那麼用後置通知就比環繞通知合適。因為,儘管環繞通知可以完成同樣的功能,但在後置通知中不用象環繞通知那樣必須呼叫IMethodInvocation介面的Proceed()方法來允許連線點繼續執行,所以連線點總是能正常執行。(按:換句話說,因為環繞通知可以控制連線點的繼續執行,所以如果沒有呼叫IMethodInvocation介面的Proceed()方法,連線點就會被“短路”;而使用後置通知就不存在這個問題)。 



切入點是AOP的關鍵概念,使AOP從根本上區別於舊的攔截技術。切入點使通知可以獨立於OO的繼承層次之外。例如,一個宣告式事務處理的環繞通知可以應用於不同物件的方法。切入點是AOP的結構性要素。

 

2.  jar

Spring框架的包,和aop相關的jar。

3.  學習例子

如在給controller做切面,記錄訪問controller時的日誌

LogAspect.java

//@Component//掃描,等價<bean>

//@Aspect//等價於<aop:aspect
ref="">

public class LogAspect {

private static Logger log = LoggerFactory.getLogger(getClass());

//@Around("within(org.tedu.cloudnot.controller..*)")

//等價於<aop:around method="logController" pointcut
= "">

public Object aroundController(ProceedingJoinPoint
joinPoint) throws
Throwable{

String targetName = joinPoint.getTarget().getClass().getName();

String methodName = joinPoint.getSignature().getName();

String description = "";

Class targetClass = targetClass.getMethods();

Method[] methods = targetClass.getMethods();

for(Method method :
methods){

if(method.getName().equals(methodName)){

Class[] clazzs = method.getParameterTypes();

for(Class<?>
clas : clazzs){

String parameterName = clas.getName();

description = description = "" + parameterName;

}

}

}

log.info("enter into
Controller---------------------------------");

log.info("targetName:" + targetName + ";");

log.info("methodName:" + methodName + ";");

log.info("parameterTypes:" + description + ";");

Object[] args = joinPoint.getArgs();

log.info("parameterValues" + Arrays.toString(args));

Object object = null;

try{

object = joinPoint.proceed();

}catch(Exception ex){

log.error("=============================controller方法執行異常==============");

BaseResult rr = new BaseResult();

rr.setErrorCode();

rr.setErrorMsg);

return rr;

}

Object[] objs = joinPoint.getArgs();

log.info("returnResult:" + object);

return object;

}

public void afterThrowingController(JoinPoint joinPoint, Throwable e){

String params = "";

if(joinPoint.getArgs() != null &&
joinPoint.getArgs().length > 0){

for(int i = 0; i<joinPoint.getArgs().length; i++){

params += joinPoint.getArgs()[i] + ";";

}

}

try{

System.out.println("------異常通知開始-------------");

System.out.println("異常程式碼");

System.out.println("異常資訊");

System.out.println("異常方法" + (joinPoint.getTarget().getClass.getName + "."

+
joinPoint.getSignature().getName()+"()"));

System.out.println("請求引數"+ params);

}catch(Exception ex){

log.error("==異常通知異常=====");

log.error("異常資訊:{}", ex.getMessage());

}

/*===========記錄本地異常日誌============*/

log.error("異常方法:{}異常程式碼:{}異常引數:{}引數{}", new
Object[]{1,2,3,4});

}

4.  spring.xml配置

<aop:config>

<aop:pointcut expression="(within(com.me..*)or

within(coom.me..*))"

id="controllerPointcut"/>

<aop:aspect id = "logAspect"
ref="loggerBean">

<aop:arount method="arountController"
pointcut-ref="contrllerPointcut"/>

<aop:after-throwing method="afterThrowingController" pointcut-ref="controllerPointcut" throwing="e"/>

</aop:aspect>

</aop:config>

*需要新增aop的schema配置

*可能需要新增aop的自定義掃描註解(這裡不需要,已測試,使用註解則需要)

*可能如果aop的切面程式無法起作用,注意可能是配置要寫在spring-servlet.xml(即springmvc的配置檔案中)

<context:component-scan base-package="com.spring.aop" />

<aop:aspectj-autoproxy />

<!-- 通知spring使用cglib而不是jdk的來生成代理方法 AOP可以攔截到Controller -->

<aop:aspectj-autoproxy proxy-target-class="true" />

4.  SpringAOP的表示式

任意公共方法的執行: 

execution(public *
*(..)) 

任何一個以“set”開始的方法的執行: 

execution(*
set*(..)) 

AccountService
介面的任意方法的執行: 

execution(*
com.xyz.service.AccountService.*(..)) 

定義在service包裡的任意方法的執行: 

execution(*
com.xyz.service.*.*(..)) 

定義在service包或者子包裡的任意方法的執行: 

execution(*
com.xyz.service..*.*(..)) 

在service包裡的任意連線點(在Spring AOP中只是方法執行) : 

within(com.xyz.service.*) 

在service包或者子包裡的任意連線點(在Spring AOP中只是方法執行) : 

within(com.xyz.service..*) 

實現了 AccountService 介面的代理物件的任意連線點(在Spring
AOP中只是方法執行) : 

this(com.xyz.service.AccountService) 

'this'在binding form中用的更多:- 請常見以下討論通知的章節中關於如何使得代理物件可以在通知體內訪問到的部分。 

實現了 AccountService 介面的目標物件的任意連線點(在Spring
AOP中只是方法執行) : 

target(com.xyz.service.AccountService) 

'target'在binding form中用的更多:- 請常見以下討論通知的章節中關於如何使得目標物件可以在通知體內訪問到的部分。 

任何一個只接受一個引數,且在執行時傳入的引數實現了
Serializable 介面的連線點 (在Spring AOP中只是方法執行) 

args(java.io.Serializable) 

'args'在binding form中用的更多:- 請常見以下討論通知的章節中關於如何使得方法引數可以在通知體內訪問到的部分。 請注意在例子中給出的切入點不同於
execution(* *(java.io.Serializable)): args只有在動態執行時候傳入引數是可序列化的(Serializable)才匹配,而execution 在傳入引數的簽名宣告的型別實現了
Serializable 介面時候匹配。 

有一個 @Transactional 註解的目標物件中的任意連線點(在Spring
AOP中只是方法執行) 

@target(org.springframework.transaction.annotation.Transactional) 

'@target' 也可以在binding form中使用:請常見以下討論通知的章節中關於如何使得annotation物件可以在通知體內訪問到的部分。 

任何一個目標物件宣告的型別有一個
@Transactional 註解的連線點(在Spring
AOP中只是方法執行) 

@within(org.springframework.transaction.annotation.Transactional) 

'@within'也可以在binding form中使用:- 請常見以下討論通知的章節中關於如何使得annotation物件可以在通知體內訪問到的部分。 

任何一個執行的方法有一個 @Transactional
annotation的連線點(在Spring
AOP中只是方法執行) 

@annotation(org.springframework.transaction.annotation.Transactional) 

'@annotation'
也可以在binding form中使用:- 請常見以下討論通知的章節中關於如何使得annotation物件可以在通知體內訪問到的部分。 

任何一個接受一個引數,並且傳入的引數在執行時的型別實現了
@Classified annotation的連線點(在Spring
AOP中只是方法執行) 

@args(com.xyz.security.Classified)