【Spring】【AOP】【面向切面程式設計】【AOP的註解】【獲取引數和修改引數】
Spring的AOP功能就是面向切面程式設計.我們從Spring容器取出的值,就是已經被重新包裝過代理物件
概念
- 通知: 要切入的內容
- 切點: 要切入的地方
- 切面織入: 將切面織入類的方法中,切面=通知+切點
通知的類
在該類中宣告各自通知,每個通知+切點,都能組成一個切面
public class MyAdvice {
//前置通知
public void before() {
System.out.println("前置通知");
}
//後置通知
public void afterReturning() {
System.out .println("後置通知");
}
//最終通知
public void after() {
System.out.println("最終通知");
}
//異常通知
public void afterThrowing() {
System.out.println("異常通知");
}
}
AOP的配置
- 注入通知類的bean
- 設定aop–宣告切點
- 設定aop–織入切面
<!-- 注入通知類的bean -->
<bean id="myAdvice" class="dao.impl.MyAdvice" />
<aop:config>
<!-- 設定1個切點by execution表示式 -->
<aop:pointcut expression="execution(* *..*.*myDowork*(..))" id="pc"/>
<!-- 織入4個切面 -->
<aop:aspect ref="myAdvice">
<aop:after method="after" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc"/>
<aop:before method="before" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
1. execution表示式
expression="execution(表示式)"
作用:定位到符合表示式的方法,將其宣告為切入點;
定位的條件有五個:
- 方法的形參型別
- 方法的返回值型別
- 方法所在的包
- 方法所在的類
- 方法的名稱
例:execution(* *..*.*myDowork*(..))
:
表示任意包,類,形參型別,返回值型別.方法名含有myDowork的方法.
例:execution(java.lang.String a.b.A.*(..))
:
表示a.b包下的A類,返回值為String的所有方法,
注意:由於我們需要匯入很多jar,所以會有很多方法名重複,儘量使用execution表示式時候,加入包名.
2. 通知型別
<aop:before>
:前置通知,方法執行前執行<aop:after-returning>
:後置通知, 方法執行完執行,(出現異常不執行)<aop:after>
:最終通知,方法執行完執行,(出現異常依然執行)<aop:after-throwing>
異常通知,方法出現異常後執行<aop:around>
環繞通知,可以一次性完成所有通知,可以修改形參,一般配合註解使用
AOP的註解
不用在Spring的配置檔案中配置,直接在通知的類中用註解方式告訴Spring織入切面方式.
註解所在的包org.aspectj.lang.annotation.
- 宣告使用註解
<aop:aspectj-autoproxy/>
- 通知的類:@Aspect
- 切入點的方法(方法名=id);@Pointcut(“execution表示式”)
- 織入切面:在通知類的方法上
- @通知型別(“切點id()”)或@通知型別(“execution表示式”的String)
- 通知型別中的屬性
- value/pointcut=切點
- throwing=”ex”,異常通知的Throwable 物件為ex -
<!-- 注入通知類的bean -->
<bean id="myAdvice" class="dao.impl.MyAdvice"/>
普通的切面織入
@Aspect
public class MyAdvice {
//宣告切點方式1 //static+final
public static final String EXP="execution(* *..*.*myDowork*(..))";
//宣告切點方式2
@Pointcut("execution(* *..*.*myDowork*(..))")
public void pc() {}
@Before("pc()")
public void before() {
System.out.println("前置通知");
}
@AfterReturning(EXP)
public void afterReturning() {
System.out.println("後置通知");
}
@After("pc()")
public void after() {
System.out.println("最終通知");
}
@AfterThrowing("pc()")
public void afterThrowing() {
System.out.println("異常通知");
}
}
異常的切面織入
@Aspect
public class MyAdvice {
//宣告切點
@Pointcut("execution(* *..*.*myDowork*(..))")
public void pc() {}
//throwing="ex"表示宣告異常的物件
@AfterThrowing(pointcut="pc()",throwing="ex")
public void afterThrowing(Throwable ex) {
System.out.println("異常通知");
System.out.println(ex.getMessage());
}
}
環繞的切面織入
@Aspect
public class MyAdvice {
//宣告切點
public static final String EXP="execution(* *..*.*myDowork*(..))";
@Around(EXP)
public Object around(ProceedingJoinPoint pjp) {
try {
System.out.println("前置通知");
Object ret = pjp.proceed(); //執行目標物件的方法
System.out.println("後置通知");
return ret; //返回值
}catch(Throwable ex) {
System.out.println("異常通知");
System.out.println(ex.getMessage());
}finally {
System.out.println("最終通知");
}
return null;
}}
引數的傳遞
作用:通過判斷引數值,來選擇不同的處理機制
1. 表示式中需要加入引數
2. 利用argNames屬性,宣告引數
3. 對於符合execution表示式,但不符合引數型別的方法,不會被織入切面
@Aspect
public class MyAdvice {
//宣告切點方式1 static+final
public static final String EXP="execution(* *..*.*myDowork*(..))&& args(str,i)";
@Before(value=EXP,argNames="str,i")
public void before2(String str,Integer i) {
System.out.println("123");
System.out.println(str+i);
}
//宣告切點方式2
@Pointcut(value="execution(* *..*.*myDowork*(..)) && args(str,i)",argNames="str,i")
public void pc(String str,Integer i) {}
@Before(value="pc(str,i)",argNames="str,i")
public void before(String str,Integer i) {
System.out.println(str+i);
}
}
引數的修改
只有在環繞通知中可以修改引數.
1.獲得引數的陣列:Object[] args = pjp.getArgs();
2.修改引數的陣列元素:args[i]=XXX;
3.執行目標物件方法時候,傳入新陣列;Object ret = pjp.proceed(args);
@Aspect
public class MyAdvice {
public static final String EXP = "execution(* *..*.*myDowork*(..))";
@Around(EXP)
public Object around(ProceedingJoinPoint pjp) {
try {
Object[] args = pjp.getArgs();
args[0]="hahah";
Object ret = pjp.proceed(args);
return ret;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
注意點:環繞對所有符合表示式的方法,都會進行織入切面
即沒有形參的方法,且符合表示式,也會被織入切面 而加入形參pjp.proceed(args);
會報錯.
- 要嚴格寫好execution表示式
- 判斷陣列物件長度和大小來進行選擇處理方式
pjp.getSignature().getDeclaringTypeName()
目標物件的許可權類名pjp.getSignature().getName()
目標物件呼叫的方法名pjp.getSignature().getDeclaringType()
目標物件的位元組碼物件