Spring核心概念<二>:AOP面向切面編程
概述
AOP和OOP的區別:面向對象編程將程序分解爲各個層次的對象,而面向切面編程將程序運行過程分解爲各個切面。可以理解爲oop是從靜態角度考慮程序結構的,而aop是從動態角度考慮程序結構的。
AOP的來源:當有多個對象同時複用一段相同的代碼段時,傳統的使用方法調用的方式無法避免耦合,所以出現了aop實現不同調用方法與複用代碼塊之間的解耦合。Aop專門用於處理系統中分佈於各個模塊中的交叉關注點的問題,常常使用AOP來處理一些具有橫切性質的系統級服務,如事務管理,安全檢查,緩存,對象池管理等
AOP的目標:在不修改源代碼的前提下,爲系統多個組件的多個方法添加某種通用的功能。
AOP分類:靜態aop(AspectJ),編譯時增強/動態aop(Spring AOP)運行時增強
基本概念
aop代理:aop框架創建的對象,簡單來說,代理就是對目標對象的增強。
織入(weaving):將增強處理添加到目標對象。根據使用框架的不同可以細分爲運行時增強和編譯時增強
AOP代理方法 = 增強處理 + 目標對象的方法
對於開發者來說,主要參與
定義普通業務組件
定義切入點,一個切入點可橫切多個頁面
定義增強處理
基於註解的“零配置”方式/xml配置方式
step1 啓動Spring對@AspectJ的支持,需要在Spring配置文件中添加如下片段:
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- 配置@AspectJ支持 -->
<aop:aspecj-autoproxy>
</beans>
step2 定義切面
@Aspect
public class logPrint{
……
}
step3 增強處理
- 定義before增強處理:每次指向目標方法之前都會執行before增強處理方法
定義一個切面類
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;
@Aspect
public class AuthAspect
{
// 所有方法的執行作爲切入點
@Before("execution(* org.crazyit.app.service.impl.*.*(..))")
public void authority()
{
System.out.println("模擬執行權限檢查");
}
}
定義一個普通方法
package org.crazyit.app.service.impl;
import org.springframework.stereotype.Component;
import org.crazyit.app.service.*;
public class HelloImpl implements Hello
{
// 定義一個簡單方法,模擬應用中的業務邏輯方法
public void foo()
{
System.out.println("執行Hello組件的foo()方法");
}
// 定義一個addUser()方法,模擬應用中的添加用戶的方法
public int addUser(String name , String pass)
{
System.out.println("執行Hello組件的addUser添加用戶:" + name);
return 20;
}
}
配置beans.xml
<?xml version="1.0" encoding="GBK"?>
<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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 指定自動搜索Bean組件、自動搜索切面類 -->
<context:component-scan base-package="org.crazyit.app.service
,org.crazyit.app.aspect">
<context:include-filter type="annotation"
expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
<!-- 啓動@AspectJ支持 -->
<aop:aspectj-autoproxy/>
通過以上三個步驟,即:定義切面類,切入點和增強方法;定義普通組件;配置beans,就可以實現一個簡單的before增強處理的aop配置
定義AfterReturning增強處理
定義AfterThrowing增強處理
After增強處理(注:與AfterReturning的區別在於,後者無論正常結束或者意外結束方法都會進行後處理)
-Aroud增強處理
可以看作是Before增強處理和AfterRerturning增強處理的總和,但是更強大,強大之處在於Around增強處理可以決定目標方法在什麼時候處理,如何處理,甚至完全阻止目標方法的執行。它也可以改變目標方法的參數值和返回值
package org.crazyit.app.aspect;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;
// 定義一個切面
@Aspect
public class TxAspect
{
// 匹配org.crazyit.app.service.impl包下所有類的、
// 所有方法的執行作爲切入點
@Around("execution(* org.crazyit.app.service.impl.*.*(..))")
public Object processTx(ProceedingJoinPoint jp)
throws java.lang.Throwable
{
System.out.println("執行目標方法之前,模擬開始事務...");
// 獲取目標方法原始的調用參數
Object[] args = jp.getArgs();
if(args != null && args.length > 1)
{
// 修改目標方法的第一個參數
args[0] = "【增加的前綴】" + args[0];
}
// 以改變後的參數去執行目標方法,並保存目標方法執行後的返回值
Object rvt = jp.proceed(args);
System.out.println("執行目標方法之後,模擬結束事務...");
// 如果rvt的類型是Integer,將rvt改爲它的平方
if(rvt != null && rvt instanceof Integer)
rvt = (Integer)rvt * (Integer)rvt;
return rvt;
}
}
如上所示,Spring的第一個參數一定是ProceedingJoinPoint jp,通過他可以取得目標對象[包括通過getArgs()取得參數,通過proceed()取得返回值];調用它的procees()方法,目標方法纔會被執行 。
取得目標方法的參數 主要包括三個方法Object[] getArgs()/Signature getSignature()/Object getTarget() 分別表示取得目標對象的參數,取得目標對象的簽名,取得被增強的目標對象
package org.crazyit.app.aspect; import org.aspectj.lang.annotation.*; import org.aspectj.lang.*; import java.util.Arrays; @Aspect public class FourAdviceTest { // 定義Around增強處理 @Around("execution(* org.crazyit.app.service.impl.*.*(..))") public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable { System.out.println("Around增強:執行目標方法之前,模擬開始事務..."); // 訪問執行目標方法的參數 Object[] args = jp.getArgs(); // 當執行目標方法的參數存在, // 且第一個參數是字符串參數 if (args != null && args.length > 0 && args[0].getClass() == String.class) { // 修改目標方法調用參數的第一個參數 args[0] = "【增加的前綴】" + args[0]; } //執行目標方法,並保存目標方法執行後的返回值 Object rvt = jp.proceed(args); System.out.println("Around增強:執行目標方法之後,模擬結束事務..."); // 如果rvt的類型是Integer,將rvt改爲它的平方 if(rvt != null && rvt instanceof Integer) rvt = (Integer)rvt * (Integer)rvt; return rvt; } // 定義Before增強處理 @Before("execution(* org.crazyit.app.service.impl.*.*(..))") public void authority(JoinPoint jp) { System.out.println("Before增強:模擬執行權限檢查"); // 返回被織入增強處理的目標方法 System.out.println("Before增強:被織入增強處理的目標方法爲:" + jp.getSignature().getName()); // 訪問執行目標方法的參數 System.out.println("Before增強:目標方法的參數爲:" + Arrays.toString(jp.getArgs())); // 訪問被增強處理的目標對象 System.out.println("Before增強:被織入增強處理的目標對象爲:" + jp.getTarget()); } //定義AfterReturning增強處理 @AfterReturning(pointcut="execution(* org.crazyit.app.service.impl.*.*(..))" , returning="rvt") public void log(JoinPoint jp , Object rvt) { System.out.println("AfterReturning增強:獲取目標方法返回值:" + rvt); System.out.println("AfterReturning增強:模擬記錄日誌功能..."); // 返回被織入增強處理的目標方法 System.out.println("AfterReturning增強:被織入增強處理的目標方法爲:" + jp.getSignature().getName()); // 訪問執行目標方法的參數 System.out.println("AfterReturning增強:目標方法的參數爲:" + Arrays.toString(jp.getArgs())); // 訪問被增強處理的目標對象 System.out.println("AfterReturning增強:被織入增強處理的目標對象爲:" + jp.getTarget()); } // 定義After增強處理 @After("execution(* org.crazyit.app.service.impl.*.*(..))") public void release(JoinPoint jp) { System.out.println("After增強:模擬方法結束後的釋放資源..."); // 返回被織入增強處理的目標方法 System.out.println("After增強:被織入增強處理的目標方法爲:" + jp.getSignature().getName()); // 訪問執行目標方法的參數 System.out.println("After增強:目標方法的參數爲:" + Arrays.toString(jp.getArgs())); // 訪問被增強處理的目標對象 System.out.println("After增強:被織入增強處理的目標對象爲:" + jp.getTarget()); } }
step4 指定不同切面類裏增強處理的優先級
讓切面實現org.springframework.core.Ordered接口,實現給該接口只需實現一個int getOrder()方法
直接時候Order註解來修飾一個切面類,指定一個int型的value屬性最爲優先級,該屬性越小優先級越高
step5 定義切入點:切入點的定義包含兩個部分 切入點表達式+包含任意名字和參數的方法簽名
@PointCut("execution(* transfer(..))")
private void anyOldTransfer(){}
上面定義了一個名爲anyOlderTransfer的切入點,可以匹配任何以transfer結尾的方法
@AfterReturning(pointcut="anyOldTransfer()",returning="retval")
public void writing(String msg,Object retval)
{
...
}
step6 切入點指示符
execution匹配執行方法連接點
within用於限定匹配特定類型的連接點
this用於限定aop代理必須是制定類型的實例
target用於限定目標對象必須是指定類型的實例
args用於對連接點參數類型進行限制
step7 組合切入點 :&& || ! 分別表示與或非