1. 程式人生 > >Spring---AOP基本概念以及Advice5種類型的通知註解應用例項

Spring---AOP基本概念以及Advice5種類型的通知註解應用例項

AOP

AOP(Aspect Oriented Programming),即面向切面程式設計,可以說是OOP(Object Oriented Programming,面向物件程式設計)的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,用於模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌程式碼往往橫向地散佈在所有物件層次中,而與它對應的物件的核心功能毫無關係對於其他型別的程式碼,如安全性、異常處理和透明的持續性也都是如此,這種散佈在各處的無關的程式碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。

AOP技術恰恰相反,它利用一種稱為”橫切”的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其命名為”Aspect”,即切面。所謂”切面”,簡單說就是那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組之間的耦合度,並有利於未來的可操作性和可維護性。
首先

在Spring中提供了面向切面程式設計的豐富支援,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。

AspectJ 中幾個必須要了解的概念:

Aspect: Aspect 宣告類似於 Java 中的類宣告,在 Aspect 中會包含著一些 Pointcut 以及相應的 Advice。

Joint point
:表示在程式中明確定義的點,典型的包括方法呼叫,對類成員的訪問以及異常處理程式塊的執行等等,它自身還可以巢狀其它 joint point。 Pointcut:表示一組 joint point,這些 joint point 或是通過邏輯關係組合起來,或是通過通配、正則表示式等方式集中起來,它定義了相應的 Advice 將要發生的地方。 Advice:Advice 定義了在 pointcut 裡面定義的程式點具體要做的操作,它通過 before、after 和 around 來區別是在每個 joint point 之前、之後還是代替執行的程式碼。 Target: 被通知的物件。通俗的說,就是與業務本身相關的原始物件。 Proxy: 向目標物件應用通知之後建立的物件。通俗的說,就是原始物件用代理物件包裝了一層又一層的物件,就是圖中最上面的一大坨方框。

AspectJ 支援 5 種類型的通知註解:

@Before : 前置通知, 在方法執行之前執行

@After : 後置通知, 在方法執行之後執行

@AfterRunning: 返回通知, 在方法返回結果之後執行

@AfterThrowing: 異常通知, 在方法丟擲異常之後

@Around : 環繞通知, 圍繞著方法執行。
環繞通知是所有通知型別中功能最為強大的, 能夠全面地控制連線點. 甚至可以控制是否執行連線點.

對於環繞通知來說, 連線點的引數型別必須是 ProceedingJoinPoint . 它是 JoinPoint 的子介面, 允許控制何時執行, 是否執行連線點.

在環繞通知中需要明確呼叫 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行.

注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即呼叫 joinPoint.proceed(); 的返回值, 否則會出現空指標異常

下面用例項來講解:

//加入jar包
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.10</version>
    </dependency>
//建立Spring配置檔案
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.lyf.C">
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"></context:include-filter>
     <!--含義是允許base-package包中使用基於面向切面的註解-->
</context:component-scan>
    <aop:aspectj-autoproxy/> <!--含義是啟用Spring對aspectj切面配置的支援-->
</beans>
package com.lyf.C;

import org.springframework.stereotype.Component;

/**
 * Created by fangjiejie on 2017/4/25.
 */
@Component
public class Animal {
    public String play(String name){
        System.out.println("動物也有娛樂活動比如:"+name);
//        System.out.println(6/0);
        return "666";
    }
}
package com.lyf.C;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;

/**
 * Created by fangjiejie on 2017/4/25.
 */
@Aspect
@Order(1)//設定橫切關注點的優先順序順序
public class Dog {
    /*
   * 在目標方法執行之前,執行該通知
   */
    @Before("execution(* com.lyf.C.Animal.*(..))")
    void bark1(){
        System.out.println("Dog來打哈哈---Before");
    }
    /*
     * 在目標方法執行之後(無論是否發生異常),執行該通知
     */
    @After("execution(* com.lyf.C.Animal.*(..))")
    void bark2(){
        System.out.println("Dog來打哈哈---After");
    }
    /*
     * 在目標方法正常執行之後執行的程式碼
     * 返回通知是可以訪問目標方法的返回值的
     */
    @AfterReturning(pointcut = "execution(* com.lyf.C.Animal.*(..))",returning = "ret")//自定義的ret必須在該方法的形參中出現
        //如果註解中引數有多個,要用逗號來連結,引數都要以鍵值對的形式存在
    void bark3(String ret){
        System.out.println("Dog來打哈哈---AfterReturning"+"---"+ret);
    }
    /*
         * 迴環通知需要攜帶ProceedingJoinPoint型別引數
         * 迴環通知類似於動態代理的全過程:ProceedingJoinPoint型別的引數可以決定是否執行目標方法
         * 且迴環通知必須有返回值,返回值即為目標方法的返回值
         */
    @Around("execution(* com.lyf.C.Animal.play(..))")
    String bark4(ProceedingJoinPoint joinPoint){
        String rs=null;
        try {
            Object os[]=joinPoint.getArgs();//可以獲取目標方法的形參
            System.out.println("Dog來打哈哈---Around--before"+"---"+os[0]);
            rs=(String) joinPoint.proceed();//以插入點形式執行,執行後用rs來保留目標方法的返回值
            System.out.println("Dog來打哈哈---Around--after"+"---"+os[0]);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
         return rs;
    }
    /*
     * 在目標方法出現異常時出現程式碼
     * 可以訪問到異常物件.
     */
    @AfterThrowing(pointcut = "execution(* com.lyf.C.Animal.play(..))",throwing = "ex")
    public void bark5(Throwable ex){
        System.out.println("丟擲異常"+ex);
    }
}
package com.lyf.C;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;

/**
 * Created by fangjiejie on 2017/4/25.
 */
@Aspect
@Order(2)
public class Cat {
    @Before("execution(* com.lyf.C.Animal.*(..))")
    void bark1(){
        System.out.println("Cat來打哈哈---Before");
    }
    @After("execution(* com.lyf.C.Animal.*(..))")
    void bark2(){
        System.out.println("Cat來打哈哈---After");
    }
    void bark3(String ret){
        System.out.println("Cat來打哈哈---AfterReturning"+"---"+ret);
    }
    @Around("execution(* com.lyf.C.Animal.play(..))")
    String bark4(ProceedingJoinPoint joinPoint){
        String rs=null;
        try {
            Object os[]=joinPoint.getArgs();//可以獲取目標方法的形參
            System.out.println("Cat來打哈哈---Around--before"+"---"+os[0]);
            rs=(String) joinPoint.proceed();//以插入點形式執行,執行後用rs來保留目標方法的返回值
            System.out.println("Cat來打哈哈---Around--after"+"---"+os[0]);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return rs;
    }
    @AfterThrowing(pointcut = "execution(* com.lyf.C.Animal.play(..))",throwing = "ex")
    public void bark5(Throwable ex){
        System.out.println("丟擲異常"+ex);
    }
}
package com.lyf.C;

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

/**
 * Created by fangjiejie on 2017/4/25.
 */
public class Test {
    public static void main(String[] args) {
        ApplicationContext act=new ClassPathXmlApplicationContext("bean1.xml");
        Animal animal=(Animal) act.getBean("animal");
        animal.play("swim");
    }
}

執行結果:

Dog來打哈哈---Around--before---swim
Dog來打哈哈---Before
Cat來打哈哈---Around--before---swim
Cat來打哈哈---Before
動物也有娛樂活動比如:swim
Cat來打哈哈---Around--after---swim
Cat來打哈哈---After
Dog來打哈哈---Around--after---swim
Dog來打哈哈---After
Dog來打哈哈---AfterReturning---666

我們可以自行從執行結果中分析出執行順序。