上一篇:Spring學習筆記(七、Spring AOP API)
一、AspectJ介紹與Pointcut注解應用
1. AspectJ
- @AspectJ的風格類似純java注解的普通java類。
- Spring可以使用AspectJ來做切入點分析。
- AOP的運行時仍舊是純的Spring AOP,對AspectJ的編譯器或者織入無依賴性。
2. Spring中配置AspectJ
- 對@AspectJ支持可以使用XML或Java風格配置。
-
確保AspectJ的aspectjweaver.jar庫包含在應用程序(版本1.6.8或更高版本)的classpath中。
Paste_Image.png
3. @Aspect注解
- AspectJ切面使用@Aspect注解配置,擁有@Aspect注解的任何bean,將被Spring自動識別并應用。
- 用@Aspect注解的類可以有方法和字段,他們也可能包括切入點(pointcut)、通知(advice)、和引入(introduction)聲明。
- @Aspect注解是不能通過類路徑自動檢測發現的,所以需要配合使用@Component注釋或者在xml中配置bean。
- 一個類中的@Aspect注解標識它為一個切面,并且將自己從自動代理中排除。
4. pointcut
- 一個切入點通過一個普通的方法定義來提供,并且切入點表達式使用@Pointcut注解,方法返回類型必須為void。
指示符 | 說明 |
---|---|
execution | 匹配方法執行的連接點 |
within | 限定匹配特定類型的連接點 |
this | 匹配特定連接點的bean引用,是指定類型的實例的限制 |
target | 限定匹配特定連接點的目標對象是指定類型的實例 |
args | 限定匹配特定連接點的參數是給定類型的實例 |
@target | 限定匹配特定連接點的類執行對象的具有給定類型的注解 |
@args | 限定匹配特定連接點實際傳入的參數的類型具有給定類型的注解 |
@within | 限定匹配到內具有給定的注釋類型的連接點 |
@annotation | 限定匹配特定連接點的主體具有給定的注解 |
創建一個切面類:AmberAspect
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
}
5. 組合pointcut
- 切入點表達式可以通過&&、||和!進行組合,也可以通過名字引用切入點表達式。
-
通過組合,可以建立更加復雜的切入點表達式。
Paste_Image.png
6. 定義良好的pointcuts
- AspectJ是編譯器的AOP。
- 檢查代碼并匹配連接點與切入點的代價是昂貴的。
- 一個好的切入點應該包括以下幾點:
- 選擇特定類型的連接點。如:execution、get、set、call、handler
- 確定連接點范圍,如:within、withincode
- 匹配上下文信息,如:this、target、@annotation
二、Advice定義及實例
1. Before advice
更新AmberAspect 切面類:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("execution(* com.amber.aop.biz.*Biz.*(..))")
public void before(){
System.out.println("前置通知!");
}
}
創建業務類AspectBiz :
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 業務類
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("執行AspectBiz的save方法,參數:"+args);
return "Save Success";
}
}
applicationContext:
<context:component-scan base-package="com.amber.aop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
測試類:
@Test
public void test17() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.save("淡雅如菊,溫潤如玉");
}
結果:
修改AmberAspect 切面類,將前置通知的表達式替換成同樣表達式的pointcut()方法:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
}
結果:
2. After Returning Advice
- 有時候需要在通知體內得到返回的實際值,可以使用@AfterReturning綁定返回值的形式。
更新AmberAspect 切面類,增加After Returning通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值為:"+returnValue);
}
}
結果:
3. After throwing advice
- 有時候需要在通知體內得到返回的實際值,可以使用@AfterThrowing綁定返回值的形式。
更新AmberAspect 切面類,增加After Throwing通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值為:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("異常后通知,異常為:"+e);
}
}
修改類:
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 業務類
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("執行AspectBiz的save方法,參數:"+args);
throw new RuntimeException("Save failed");
//return "Save Success";
}
}
結果:
3. After(finally) advice
- 最終通知必須準備處理正常和異常兩種返回情況,它通常用于釋放資源。
更新AmberAspect 切面類,增加After通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值為:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("異常后通知,異常為:"+e);
}
@After("bizPointcut()")
public void after(){
System.out.println("后置通知!");
}
}
結果:
將業務類AspectBiz異常注釋:
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 業務類
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("執行AspectBiz的save方法,參數:"+args);
// throw new RuntimeException("Save failed");
return "Save Success";
}
}
結果:
4. Around advice
- 環繞通知使用@Around注解來聲明,通知方法的第一個參數必須是ProceedingJoinPoint類型。
- 在通知內部會調用ProceedingJoinPoint的proceed()方法會導致執行真正的方法,傳入一個Object[]對象,數組中的值將被作為參數傳遞給方法。
更新AmberAspect ,增加Around通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值為:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("異常后通知,異常為:"+e);
}
@After("bizPointcut()")
public void after(){
System.out.println("后置通知!");
}
@Around("bizPointcut()")
public Object around(ProceedingJoinPoint pjp)throws Throwable{
System.out.println("環繞通知,pjp.proceed();執行前!");
Object obj=pjp.proceed();
System.out.println("環繞通知,pjp.proceed();執行后! 返回值為:"+obj);
return obj;
}
}
結果:
三、給Advice傳遞參數
1. 給Advice傳遞參數
- 創建一個AmberAspectTwo切面類,增加帶參的前置通知,傳入普通參數,將之前的AmberAspect類的@Aspect注釋掉:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo{
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut() {
}
@Pointcut("pointcut()&&args(arg)")
public void before(String arg) {
System.out.println("前置通知,獲取參數為:" + arg);
}
}
結果:
- 創建一個自定義注解:
package com.amber.aop.aspectj;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by amber on 2017/6/20.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AmberMethod {
String value();
}
修改業務類方法,為其添加一個自定義注解:
package com.amber.aop.biz;
import com.amber.aop.aspectj.AmberMethod;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 業務類
*/
@Service
public class AspectBiz {
@AmberMethod("這是我自定義的注解")
public String save(String args){
System.out.println("執行AspectBiz的save方法,參數:"+args);
// throw new RuntimeException("Save failed");
return "Save Success";
}
}
更新AmberAspectTwo切面類,增加帶注解參數的后置通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut() {
}
@Before("pointcut()&&args(arg)")
public void beforeWithArgs(String arg) {
System.out.println("前置通知,獲取參數為:" + arg);
}
@After("pointcut()&&@annotation(amberMethod)")
public void afterWithAnnotation(AmberMethod amberMethod) {
System.out.println("后置通知,獲取參數為:" + amberMethod.value());
}
}
結果:
修改AmberAspectTwo 切面類修改其后置通知的值引用bizPointcut()方法的組合切入點。
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*) && @annotation(amberMethod)")
public void bizPointcut(AmberMethod amberMethod) {
}
@Before("pointcut()&&args(arg)")
public void beforeWithArgs(String arg) {
System.out.println("前置通知,獲取參數為:" + arg);
}
@After("bizPointcut(amberMethod)")
public void afterWithAnnotation(AmberMethod amberMethod) {
System.out.println("后置通知,獲取參數為:" + amberMethod.value());
}
}
結果:
2. Advice的參數及泛型
-
Spring AOP可以處理泛型類的聲明和使用方法的參數。
Paste_Image.png
3. Advice參數名稱
-
通知和切入點有一個額外的“argName”屬性,它可以用來指定所注解的方法的參數名。
Paste_Image.png -
如果第一個參數是JoinPoint,ProceedingJoinPoint,JoinPoint.StaticPart,那么可以忽略它。
Paste_Image.png
3. Introductions
- 允許一個切面聲明一個通知對象實現指定接口,并且提供了一個接口實現類來代表這些對象。
-
Introduction使用@DeclareParents進行注解,這個注解用來定義匹配的類型擁有一個新的parent。
Paste_Image.png
4. 切面實例化模型
- 這是一個高級主題
- “perthis” 切面通過指定@Aspect注解perthis子句實現。
- 每個獨立的service對象執行時都會創建一個切面實例。
-
service對象的每個方法在第一次執行的時候創建切面實例,切面在service對象失效的時候同時失效。
Paste_Image.png