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
我們可以自行從執行結果中分析出執行順序。