###1. spring aop支持AspectJ
- 啓用@AspectJ支持
@Configuration
@ComponentScan("com.good.aop.demo4") // 爲了引入AnimalService
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig4 {
}
- 聲明一個Aspect
- 申明一個pointCut,切入點表達式由@Pointcut註釋表示。切入點聲明由兩部分組成:一個簽名包含名稱和任何參數,以及一個切入點表達式,該表達式確定我們對哪個方法執行感興趣。
- 申明一個Advice通知,advice通知與pointcut切入點表達式相關聯,並在切入點匹配的方法執行@Before之前、@After之後或前後運行。
@Aspect
@Component
public class LoggingAspect {
/**
* 2.定義一個方法, 用於聲明切入點表達式. 一般地,
* 該方法中再不需要添入其他的代碼.
* 使用 @Pointcut 來聲明切入點表達式.
* 後面的其他通知直接使用方法名來引用當前的切入點表達式.
*/
@Pointcut("execution(* com.good.aop.demo4.service.impl.*.*(..))")
public void declareJointPointExpression(){}
/**
* 3.前置通知
* 在 com.atguigu.spring.aop.ArithmeticCalculator
* 接口的每一個實現類的每一個方法開始之前執行一段代碼
* 用通配符*來表示所有
*/
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("before method " + methodName + " begin with:" + Arrays.asList(args));
}
/**
* 後置通知
* 在方法執行之後執行的代碼,無論該方法是否出現異常,類似於finally的作用;
* @param joinPoint
*/
@After("execution(public double com.good.aop.demo4.service.impl.*.*(..))")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(" after method " + methodName +
" end " + Arrays.asList(args));
}
/**
* 返回通知
* 在方法法正常結束受執行的代碼
* 返回通知是可以訪問到方法的返回值的!
*/
@AfterReturning(value = "execution(public double com.good.aop.demo4.service.impl.*.*(..))",
returning = "result")
public void afterReturning(JoinPoint joinPoint,
Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("afterReturning The method " + methodName + " ends with " + result);
}
/**
* 異常通知
* 在目標方法出現異常時會執行的代碼.
* 可以訪問到異常對象; 且可以指定在出現特定異常時在執行通知代碼
*/
@AfterThrowing(value = "execution(public double com.good.aop.demo4.service.impl.*.*(..))", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("afterThrowing The method " + methodName + " occurs excetion:" + e);
}
/**
* 環繞通知需要攜帶 ProceedingJoinPoint 類型的參數.
* 環繞通知類似於動態代理的全過程: ProceedingJoinPoint 類型的參數可以決定是否執行目標方法.
* 且環繞通知必須有返回值, 返回值即爲目標方法的返回值
*/
@Around("execution(public double com.good.aop.demo4.service.impl.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd) {
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("around The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//執行目標方法
result = pjd.proceed();
//返回通知
System.out.println("around The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//異常通知
System.out.println("around The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//後置通知
System.out.println("around The method " + methodName + " ends");
return result;
}
}
補充一下被切面的方法:
public interface ArithmeticCalculator {
double plus(int i, int j);
double sub(int i, int j);
double multi(int i, int j);
double div(int i, int j);
}
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
public double plus(int i, int j) {
double result = i + j;
return result;
}
public double sub(int i, int j) {
double result = i - j;
return result;
}
public double multi(int i, int j) {
double result = i * j;
return result;
}
public double div(int i, int j) {
double result = i / j;
return result;
}
}
測試類:
public class TestDemo4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig4.class);
ArithmeticCalculator bean = context.getBean(ArithmeticCalculator.class);
System.out.println(bean.div(2, 2));
System.out.println(bean.div(2, 0));
}
}
打印結果:
around The method div begins with [2, 2]
before method div begin with:[2, 2]
around The method div ends with 1.0
around The method div ends
after method div end [2, 2]
afterReturning The method div ends with 1.0
1.0
around The method div begins with [2, 0]
before method div begin with:[2, 0]
around The method div occurs exception:java.lang.ArithmeticException: / by zero
after method div end [2, 0]
afterThrowing The method div occurs excetion:java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
2. 各種連接點joinPoint的意義:
1. execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這裏問號表示當前項可以有也可以沒有,其中各項的語義如下
modifiers-pattern:方法的可見性,如public,protected;
ret-type-pattern:方法的返回值類型,如int,void等;
declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
name-pattern:方法名類型,如buisinessService();
param-pattern:方法的參數類型,如java.lang.String;
throws-pattern:方法拋出的異常類型,如java.lang.Exception;
example:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和類的public 無方法參數的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和類的第一個參數爲String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te開頭的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
關於這個表達式的詳細寫法,可以腦補也可以參考官網很容易的,可以作爲一個看spring官網文檔的入門,打破你害怕看官方文檔的心理,其實你會發覺官方文檔也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由於Spring切面粒度最小是達到方法級別,而execution表達式可以用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的信息,並且在Spring中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最爲廣泛的
2. within
// 表達式的最小粒度爲類,其參數爲全路徑的類名(可使用通配符)
// ------------
// within與execution相比,粒度更大,僅能實現到包和接口、類級別。而execution可以精確到方法的返回值,參數個數、修飾符、參數類型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3. args
args表達式的作用是匹配指定參數類型和指定參數數量的方法,與包名和類名無關
/**
* args同execution不同的地方在於:
* args匹配的是運行時傳遞給方法的參數類型,args指定的參數必須是全路徑的
* execution(* *(java.io.Serializable))匹配的是方法在聲明時指定的方法參數類型。
*/
@Pointcut("args(java.io.Serializable)")//匹配運行時傳遞的參數類型爲指定類型的、且參數個數和順序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一個參數,並且傳遞的參數的運行時類型具有@Classified
3.4 this和target
this和target需要放在一起進行講解,主要目的是對其進行區別。this和target表達式中都只能指定類或者接口,在面向切面編程規範中,this表示匹配調用當前切點表達式所指代對象方法的對象,target表示匹配切點表達式指定類型的對象。比如有兩個類A和B,並且A調用了B的某個方法,如果切點表達式爲this(B),那麼A的實例將會被匹配,也即其會被使用當前切點表達式的Advice環繞;如果這裏切點表達式爲target(B),那麼B的實例也即被匹配,其將會被使用當前切點表達式的Advice環繞。
在講解Spring中的this和target的使用之前,首先需要講解一個概念:業務對象(目標對象)和代理對象。對於切面編程,有一個目標對象,也有一個代理對象,目標對象是我們聲明的業務邏輯對象,而代理對象是使用切面邏輯對業務邏輯進行包裹之後生成的對象。如果使用的是Jdk動態代理,那麼業務對象和代理對象將是兩個對象,在調用代理對象邏輯時,其切面邏輯中會調用目標對象的邏輯;如果使用的是Cglib代理,由於是使用的子類進行切面邏輯織入的,那麼只有一個對象,即織入了代理邏輯的業務類的子類對象,此時是不會生成業務類的對象的。
在Spring中,其對this的語義進行了改寫,即如果當前對象生成的代理對象符合this指定的類型,那麼就爲其織入切面邏輯。簡單的說就是,this將匹配代理對象爲指定類型的類。target的語義則沒有發生變化,即其將匹配業務對象爲指定類型的類。如下是使用this和target表達式的簡單示例:
this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)
通過上面的講解可以看出,this和target的使用區別其實不大,大部分情況下其使用效果是一樣的,但其區別也還是有的。Spring使用的代理方式主要有兩種:Jdk代理和Cglib代理。針對這兩種代理類型,關於目標對象與代理對象,理解如下兩點是非常重要的:
- 如果目標對象被代理的方法是其實現的某個接口的方法,那麼將會使用Jdk代理生成代理對象,此時代理對象和目標對象是兩個對象,並且都實現了該接口;
- 如果目標對象是一個類,並且其沒有實現任意接口,那麼將會使用Cglib代理生成代理對象,並且只會生成一個對象,即Cglib生成的代理類的對象。
結合上述兩點說明,這裏理解this和target的異同就相對比較簡單了。我們這裏分三種情況進行說明: - this(SomeInterface)或target(SomeInterface):這種情況下,無論是對於Jdk代理還是Cglib代理,其目標對象和代理對象都是實現SomeInterface接口的(Cglib生成的目標對象的子類也是實現了SomeInterface接口的),因而this和target語義都是符合的,此時這兩個表達式的效果一樣;
- this(SomeObject)或target(SomeObject),這裏SomeObject沒實現任何接口:這種情況下,Spring會使用Cglib代理生成SomeObject的代理類對象,由於代理類是SomeObject的子類,子類的對象也是符合SomeObject類型的,因而this將會被匹配,而對於target,由於目標對象本身就是SomeObject類型,因而這兩個表達式的效果一樣;
- this(SomeObject)或target(SomeObject),這裏SomeObject實現了某個接口:對於這種情況,雖然表達式中指定的是一種具體的對象類型,但由於其實現了某個接口,因而Spring默認會使用Jdk代理爲其生成代理對象,Jdk代理生成的代理對象與目標對象實現的是同一個接口,但代理對象與目標對象還是不同的對象,由於代理對象不是SomeObject類型的,因而此時是不符合this語義的,而由於目標對象就是SomeObject類型,因而target語義是符合的,此時this和target的效果就產生了區別;這裏如果強制Spring使用Cglib代理,因而生成的代理對象都是SomeObject子類的對象,其是SomeObject類型的,因而this和target的語義都符合,其效果就是一致的。
關於this和target的異同,我們使用如下示例進行簡單演示:
// 目標類
public class Apple {
public void eat() {
System.out.println("Apple.eat method invoked.");
}
}
// 切面類
@Aspect
public class MyAspect {
@Around("this(com.good.apo.demo5.service.AppleService)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("this is before around advice");
Object result = pjp.proceed();
System.out.println("this is after around advice");
return result;
}
}
// 驅動類
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
AppleService fruit = (AppleService) ctx.getBean("appleService");
fruit.eat();
}
}
執行驅動類中的main方法,結果如下:
this is before around advice
AppleService.eat method invoked.
this is after around advice
上述示例中,Apple沒有實現任何接口,因而使用的是Cglib代理,this表達式會匹配Apple對象。這裏將切點表達式更改爲target,還是執行上述代碼,會發現結果還是一樣的:
@Around("target(com.good.apo.demo5.service.AppleService)")
如果我們對Apple的聲明進行修改,使其實現一個接口,那麼這裏就會顯示出this和target的執行區別了:
public interface PearService {
void eat();
}
@Service
public class PearServiceImpl implements PearService {
@Override
public void eat() {
System.out.println("PearService.eat method invoked.");
}
}
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
PearService fruit = (PearService) ctx.getBean("pearServiceImpl");
fruit.eat();
}
}
我們還是執行上述代碼,對於this表達式,其執行結果如下:
PearService.eat method invoked.
對於target表達式,其執行結果如下:
this is before around advice
PearService.eat method invoked.
this is after around advice
可以看到,這種情況下this和target表達式的執行結果是不一樣的,這正好符合我們前面講解的第三種情況。
參考:
https://www.cnblogs.com/happyflyingpig/p/8023148.html
https://shimo.im/docs/Nj0bcFUy3SYyYnbI/read