Spring AOP 五大通知類型

1.前置通知

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

前置通知方法,能夠沒有參數,也能夠額外接收一個JoinPoint,Spring會自動將該對象傳入,表明當前的鏈接點,經過該對象能夠獲取目標對象 和 目標方法相關的信息。express

注意,若是接收JoinPoint,必須保證其爲方法的第一個參數,不然報錯。設計模式

配置方式:app

複製代碼
<?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"); // 一個鏈接點
    }
}
複製代碼

執行結果:ide

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

2.環繞通知

在目標方法執行以前和以後均可以執行額外代碼的通知。post

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

**要注意,只有環繞通知能夠接收ProceedingJoinPoint,而其餘通知只能接收JoinPoint。spa

環繞通知須要返回返回值,不然真正調用者將拿不到返回值,只能獲得一個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>
複製代碼
<?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>
複製代碼
package cn.tedu.service;
/**
 * 接口
 */
public interface UserService {
    public String addUser(String name);
    public void updateUser();
    public void deleteUser();
    public void query();
}
複製代碼
package cn.tedu.service;
/**
 * 接口
 */
public interface UserService {
    public String addUser(String name);
    public void updateUser();
    public void deleteUser();
    public void query();
}
複製代碼
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("查詢用戶。。");
    }
}
複製代碼
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("查詢用戶。。");
    }
}
複製代碼
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+"]...");
    }
}
複製代碼
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+"]...");
    }
}
複製代碼
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);
    }
}
複製代碼
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);
    }
}
複製代碼

五種通知的執行順序

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

前置通知

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

目標方法

環繞通知的調用目標方法以後的代碼

後置通知

最終通知

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

前置通知

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

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

最終通知

3.若是存在多個切面

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

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

 

若是目標方法拋出異常:

五種通知的常見使用場景

環繞通知

控制事務 權限控制

後置通知

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

異常通知

異常處理 控制事務

最終通知

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

 
 
posted on  2018
相關文章
相關標籤/搜索