AOP 執行順序:
同一aspect,不同advice的執行順序
不同aspect,advice的執行順序
詳情可見,《Spring官方文檔》
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-ordering
Spring AOP通過指定aspect的優先級,來控制不同aspect,advice的執行順序,有兩種方式:
Aspect 類添加註解:org.springframework.core.annotation.Order,使用註解value屬性指定優先級。
Aspect 類實現接口:org.springframework.core.Ordered,實現 Ordered 接口的 getOrder() 方法。
其中,數值越低,表明優先級越高,@Order 默認爲最低優先級,即最大數值:
/**
* Useful constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
最終,不同aspect,advice的執行順序:
入操作(Around(接入點執行前)、Before),優先級越高,越先執行;
一個切面的入操作執行完,才輪到下一切面,所有切面入操作執行完,纔開始執行接入點;
出操作(Around(接入點執行後)、After、AfterReturning、AfterThrowing),優先級越低,越先執行。
一個切面的出操作執行完,才輪到下一切面,直到返回到調用點;
第一項目結構:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 引入父類公用pom -->
<parent>
<groupId>universal.program.parent</groupId>
<artifactId>universal-program-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>universal.springdemo</groupId>
<artifactId>universal-springdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 單元測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>User</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
</project>
java service and serviceImpl
package com.service;
public interface UserService {
public int getUserById(int i);
public void getUserByName();
public void updateUser();
public void deleteUserById();
}
===================================================
package com.service;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
public class UserServiceImpl implements UserService {
@Override
public int getUserById(int i) {
System.out.println("======getUserById======");
int sum = i+100;
return sum;
}
@Override
public void getUserByName() {
System.out.println("======getUserByName======");
}
@Override
public void updateUser() {
System.out.println("======updateUser======");
}
@Override
public void deleteUserById() {
System.out.println("======deleteUserById======");
}
}
AOP 類:
package com.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 把這個類聲明爲一個切面:
* 1、需要把該類放入到容器中(就是加上@Component註解)
* 2、再聲明爲一個切面(加上@AspectJ註解)
*
* @author java
* @date 2018/6/3 23:20
*/
@Aspect
@Component
public class LoggingAspect {
//聲明該方法爲一個前置通知:在目標方法開始之前執行
//execution中是AspectJ表達式
//也可@Before(value = "execution(* com.Impl.ArithmeticCalculatorImpl.add(..))")
@Before(value = "execution(* com.service.UserServiceImpl.getUserById(int))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("beforeMethod "+methodName+" start with "+args);
}
//後置通知,就是在目標方法執行之後(無論是否發生異常)執行的通知
//後置通知中不能訪問目標方法的返回結果
//也可@After(value = "execution(* com.Impl.ArithmeticCalculatorImpl.add(..))")
@After(value = "execution(* com.service.UserServiceImpl.getUserById(int))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("afterMethod "+methodName+" end with "+args);
}
//返回通知,在方法正常結束之後執行的代碼
//返回通知是可以訪問到方法的返回值的
//2. returning 自定義的變量,標識目標方法的返回值自定義變量名必須和通知方法的形參一樣
//也可@AfterReturning(value = "execution(* com.Impl.ArithmeticCalculatorImpl.add(..))")
@AfterReturning(value = "execution(* com.service.UserServiceImpl.getUserById(int))",returning = "sum")
public void afterReturning(JoinPoint joinPoint,Object sum){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("afterReturning "+methodName+" end with "+sum);
}
//返回異常通知,返回拋出異常的時候執行的通知,可以獲得返回的異常
//可以訪問到異常對象,且可以指定在出現特定異常的時候再執行通知代碼
//也可@AfterThrowing(value = "execution(* com.Impl.ArithmeticCalculatorImpl.add(..))")
@AfterThrowing(value = "execution(* com.service.UserServiceImpl.getUserById(int)))",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("afterThrowing "+methodName+" end with "+ ex );
}
//環繞通知需要攜帶ProceedingJoinPoint類型的參數
//環繞通知類似於動態代理的全過程,這個類型ProceedingJoinPoint的參數可以決定是否執行目標方法
//且環繞通知必須有返回值,返回值即爲目標方法返回值
//也可@Around(value = "execution(* com.Impl.ArithmeticCalculatorImpl.add(..))")
@Around(value = "execution(* com.service.UserServiceImpl.getUserById(int))")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Object sum = null;
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
//執行目標方法
try {
//前置通知
System.out.println("around beforeMethod "+methodName+" start with "+args);
sum = proceedingJoinPoint.proceed();
//返回通知
System.out.println("around afterMethod "+methodName+" end with "+sum);
} catch (Throwable throwable) {
//異常通知
System.out.println("around afterThrowing "+methodName+" exception with "+ throwable );
throwable.printStackTrace();
}
//後置通知
System.out.println("around afterMethod "+methodName+" end with "+args);
//System.out.println("around "+proceedingJoinPoint);
return sum;
}
}
main啓動類:
package com;
import com.entity.Student;
import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo {
public static void main(String[] args) {
// ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
// Student stu = (Student) ctx.getBean("studentidtwo");
//System.out.println(stu.toString());
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/aop.xml");
UserService userService = (UserService) ctx.getBean("userServiceImpl");
int sum = userService.getUserById(100);
System.out.println("main vlaue:"+sum);
}
}
執行結果:
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@728938a9: startup date [Fri Jul 02 16:42:57 CST 2021]; root of context hierarchy
七月 02, 2021 4:42:57 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring/aop.xml]
around beforeMethod getUserById start with [Ljava.lang.Object;@243c4f91
beforeMethod getUserById start with [100]
======getUserById======
around afterMethod getUserById end with 200
around afterMethod getUserById end with [Ljava.lang.Object;@243c4f91
afterMethod getUserById end with [100]
afterReturning getUserById end with 200
main vlaue:200
Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Users\XXXXXXXXXXXXXXXX
Process finished with exit code 0
==============================注意事項===============================
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
at com.sun.proxy.$Proxy7.div(Unknown Source)
at lzj.com.spring.aop.Main.main(Main.java:12)
從輸出結果中可以看出,環繞通知實現了上面幾種通知的結合。
當div目標方法出現異常時,在環繞通知方法中已經用try…catch方法進行捕捉了,爲什麼最後輸出結果中還出現了一個返回類型不匹配的錯誤:
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
at com.sun.proxy.$Proxy7.div(Unknown Source)
at lzj.com.spring.aop.Main.main(Main.java:12)
那是因爲在環繞通知方法中開始就定義了目標方法的返回結果
Object result = null。當目標方法出現異常時,result = pdj.proceed();執行時出現異常,此時result中還是null,所以在環繞通知方法最後return result;時,返回的result就是null,但是環繞通知的返回類型我們定義的是Object類型的,null不能轉化爲Object類型,所以拋出了個類型轉換的錯誤。我們可以在環繞通知方法中把異常拋出去,即爲:
@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pdj){
/*result爲連接點的放回結果*/
Object result = null;
String methodName = pdj.getSignature().getName();
/*前置通知方法*/
System.out.println("前置通知方法>目標方法名:" + methodName + ",參數爲:" + Arrays.asList(pdj.getArgs()));
/*執行目標方法*/
try {
result = pdj.proceed();
/*返回通知方法*/
System.out.println("返回通知方法>目標方法名" + methodName + ",返回結果爲:" + result);
} catch (Throwable e) {
/*異常通知方法*/
System.out.println("異常通知方法>目標方法名" + methodName + ",異常爲:" + e);
/*當環繞通知方法本身還有其它異常時,非連接點方法出現的異常,此時拋出來*/
throw new RuntimeException();
}
/*後置通知*/
System.out.println("後置通知方法>目標方法名" + methodName);
return result;
}
}
在輸出結果中會拋出一個運行時異常java.lang.RuntimeException
插曲:不可以在執行目標方法時在定義result變量:
……
/*執行目標方法*/
try {
Object result = pdj.proceed();
……
} catch (Throwable e) {
……
}
……
return result;