Spring AOP 配置 @AspectJ支持的5中通知: —@Before:前置通知在方法執行前執行 —@After:後置通知,在方法執行後執行 —@AfterReturning:返回通知

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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章