Spring AOP 五大通知類型


1.前置通知

在目標方法執行之前執行執行的通知。

前置通知方法,可以沒有參數,也可以額外接收一個JoinPoint,Spring會自動將該對象傳入,代表當前的連接點,通過該對象可以獲取目標對象 和 目標方法相關的信息

注意,如果接收JoinPoint,必須保證其為方法的第一個參數,否則報錯。

配置方式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    ">
    
    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan>
    
    <aop:config proxy-target-class="true">
        <!-- 配置切入點  -->
        <aop:pointcut 
            expression="execution(* cn.tedu.service.UserServiceImpl.addUser(..))" 
            id="pc01"/>
            
        <!-- 配置切面 -->
        <aop:aspect ref="firstAspect">
       <<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pc01"/> </aop:aspect> </aop:config> </beans>
package cn.tedu.service;

import org.springframework.stereotype.Service;
/**
 * UserServiceImple:目標對象
 */
@Service("userService")
public class UserServiceImple implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("增加用戶。。");
    }

    @Override
    public void updateUser() {
        System.out.println("修改用戶。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("刪除用戶。。");
    }

    @Override
    public void query() {
        System.out.println("查詢用戶。。");
    }
}
package cn.tedu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
/**
 * FirstAspect:切面代碼
 */
@Component
public class FirstAspect {
    public void before(JoinPoint jp){ // 可以選擇額外的傳入一個JoinPoint連接點對象,必須用方法的第一個參數接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 通過JoinPoint對象獲取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...["+clz+"]...["+name+"]...");
    }
}
package cn.tedu.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.tedu.service.UserService;
/**
 * AOPTest:測試代碼
 */
public class AOPTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.addUser("cjj"); // 一個連接點
    }
}

執行結果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
增加用戶。。

2.環繞通知

在目標方法執行之前和之后都可以執行額外代碼的通知。

在環繞通知中必須顯式的調用目標方法,目標方法才會執行,這個顯式調用時通過ProceedingJoinPoint來實現的,可以在環繞通知中接收一個此類型的形參,spring容器會自動將該對象傳入,注意這個參數必須處在環繞通知的第一個形參位置

**要注意,只有環繞通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint

環繞通知需要返回返回值,否則真正調用者將拿不到返回值,只能得到一個null

環繞通知有控制目標方法是否執行、有控制是否返回值、有改變返回值的能力。

環繞通知雖然有這樣的能力,但一定要慎用,不是技術上不可行,而是要小心不要破壞了軟件分層的“高內聚 低耦合”的目標。

配置方式:

 <!-- 環繞通知 -->
 <aop:around method="around" pointcut-ref="pc1"/>
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--顯式的調用目標方法
        System.out.println("1 -- around after...");
        return obj;
    }

運行結果:

1 -- around before...
增加用戶。。
1 -- around after...

3.后置通知

在目標方法執行之后執行的通知。

在后置通知中也可以選擇性的接收一個JoinPoint來獲取連接點的額外信息,但是這個參數必須處在參數列表的第一個

 配置方式:

<!-- 后置通知 -->
<aop:after-returning method="afterReturn" pointcut-ref="pc1"/>
    public void afterReturn(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...");
    }

執行結果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增加用戶。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...

在后置通知中,還可以通過配置獲取返回值

一定要保證JoinPoint處在參數列表的第一位,否則拋異常

配置方式:

<!-- 后置通知 -->
<aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
    public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }

執行結果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增加用戶。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]...

4.異常通知

在目標方法拋出異常時執行的通知

可以配置傳入JoinPoint獲取目標對象和目標方法相關信息,但必須處在參數列表第一位

另外,還可以配置參數,讓異常通知可以接收到目標方法拋出的異常對象。

配置方法:

<!-- 異常通知 -->
<aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
    public void afterThrow(JoinPoint jp,Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }

代碼報異常后

執行結果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
1 -- afterThrow..[class cn.tedu.service.UserServiceImple]..[addUser]../ by zero

5.最終通知

是在目標方法執行之后執行的通知

和后置通知不同之處在於,后置通知是在方法正常返回后執行的通知,如果方法沒有正常返-例如拋出異常,則后置通知不會執行。

最終通知無論如何都會在目標方法調用過后執行,即使目標方法沒有正常的執行完成

另外,后置通知可以通過配置得到返回值,而最終通知無法得到。

最終通知也可以額外接收一個JoinPoint參數,來獲取目標對象和目標方法相關信息,但一定要保證必須是第一個參數

配置方式:

<!-- 最終通知 -->
<aop:after method="after" pointcut-ref="pc1" />
    public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }

執行結果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增加用戶。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]...
1 -- after..[class cn.tedu.service.UserServiceImple]..[addUser]...
cjj

源碼

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd "
    >

    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan>
    
    <!-- proxy-target-class屬性值決定是基於接口的還是基於類的代理被創建 -->
    <aop:config proxy-target-class="true"> 
        <!-- 配置切入點 -->
        <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImple.addUser(..))" id="pc1"/>        
        
        <!-- 配置切入面 -->
        <aop:aspect ref="firstAspect">
            <!-- 前置通知 -->    
            <aop:before method="before" pointcut-ref="pc1"/>
            
            <!-- 環繞通知 -->
            <aop:around method="around" pointcut-ref="pc1"/>
            
            <!-- 后置通知 -->
            <!-- <aop:after-returning method="afterReturn" pointcut-ref="pc1"/> -->
            <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
        
            <!-- 異常通知 -->
            <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
            
            <!-- 最終通知 -->
            <aop:after method="after" pointcut-ref="pc1" />
        </aop:aspect>
    
    </aop:config>
    
</beans>
applicationContext.xml
package cn.tedu.service;
/**
 * 接口
 */
public interface UserService {
    public String addUser(String name);
    public void updateUser();
    public void deleteUser();
    public void query();
}
UserService.java
package cn.tedu.service;

import org.springframework.stereotype.Service;
/**
 * UserServiceImple:目標對象
 */
@Service("userService")
public class UserServiceImple implements UserService {

    @Override
    public  String addUser(String name) {
        // int i = 1/0;
        System.out.println("增加用戶。。");
        return "cjj";
    }

    @Override
    public void updateUser() {
        System.out.println("修改用戶。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("刪除用戶。。");
    }

    @Override
    public void query() {
        System.out.println("查詢用戶。。");
    }
}
UserServiceImple.java
package cn.tedu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
/**
 * FirstAspect:切面代碼
 */
@Component
public class FirstAspect {
    public void before(JoinPoint jp){ // 可以選擇額外的傳入一個JoinPoint連接點對象,必須用方法的第一個參數接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 通過JoinPoint對象獲取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...["+clz+"]...["+name+"]...");
    }
    
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--顯式的調用目標方法
        System.out.println("1 -- around after...");
        return obj;
    }
    
    public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }
    
    public void afterThrow(JoinPoint jp,Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }
    
    public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }
}
FirstAspect.java
package cn.tedu.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.tedu.service.UserService;
/**
 * AOPTest:測試代碼
 */
public class AOPTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        String result = userService.addUser("cjj"); // 一個連接點
        System.out.println(result);
    }
}
AOPTes.java

五種通知的執行順序

1.在目標方法沒有拋出異常的情況下

前置通知

環繞通知的調用目標方法之前的代碼

目標方法

環繞通知的調用目標方法之后的代碼

后置通知

最終通知

2.在目標方法拋出異常的情況下

前置通知

環繞通知的調用目標方法之前的代碼

目標方法 拋出異常 異常通知

最終通知

3.如果存在多個切面

多切面執行時,采用了責任鏈設計模式。

切面的配置順序決定了切面的執行順序,多個切面執行的過程,類似於方法調用的過程,在環繞通知的proceed()執行時,去執行下一個切面或如果沒有下一個切面執行目標方法,從而達成了如下的執行過程:

 

如果目標方法拋出異常:

五種通知的常見使用場景

環繞通知

控制事務 權限控制

后置通知

記錄日志(方法已經成功調用)

異常通知

異常處理 控制事務

最終通知

記錄日志(方法已經調用,但不一定成功)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM