AOP (面向切面編程)
在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
1.AOP相關術語
-
Joinpoint
(連接點) :所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。 -
Pointcut
(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。即在動態代理中被增強的連接點就是切入點,而被過濾掉的,沒有被增強修改的方法就不是切入點。所有的切入點是連接點,但不是所有的連接點都是切入點。
-
Advice
(通知/增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
-
Introduction
(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方法或 Field。 -
Target
( 目標對象):代理的目標對象。上圖的accountService
就是target -
Weaving
( 織入):是指把增強應用到目標對象來創建新的代理對象的過程。spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。
-
Proxy
(代理):一個類被 AOP 織入增強後,就產生一個結果代理類。 -
Aspect
( 切面) : 是切入點和通知(引介)的結合。
2. 使用xml配置AOP
步驟
-
把通知Bean也交給spring來管理
-
使用
aop:config
標籤表明開始AOP的配置 -
使用
aop:aspect
標籤表明配置切面id
屬性:是給切面提供一個唯一標識ref
屬性:是指定通知類bean的Id。- 在
aop:aspect
標籤的內部使用對應標籤來配置通知的類型
aop:before
:表示配置前置通知
method
屬性:用於指定Logger類中哪個方法是前置通知
pointcut
屬性:用於指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強
切入點表達式的寫法: 關鍵字:execution(表達式) 表達式: 訪問修飾符 返回值 包名.包名.包名...類名.方法名(參數列表) 標準的表達式寫法: public void com.itheima.service.impl.AccountServiceImpl.saveAccount() 訪問修飾符可以省略 void com.itheima.service.impl.AccountServiceImpl.saveAccount() 返回值可以使用通配符,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount() 包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*. * *.*.*.*.AccountServiceImpl.saveAccount()) 包名可以使用..表示當前包及其子包 * *..AccountServiceImpl.saveAccount() 類名和方法名都可以使用*來實現通配 * *..*.*() 參數列表: 可以直接寫數據類型: 基本類型直接寫名稱 int 引用類型寫包名.類名的方式 java.lang.String 可以使用通配符表示任意類型,但是必須有參數 可以使用..表示有無參數均可,有參數可以是任意類型 全通配寫法: * *..*.*(..) 實際開發中切入點表達式的通常寫法: 切到業務層實現類下的所有方法 * com.itheima.service.impl.*.*(..)
-
aop:after-returning
後置通知:在切入點方法正常執行之後值。它和異常通知永遠只能執行一個 -
aop:after-throwing
異常通知:在切入點方法執行產生異常之後執行。它和後置通知永遠只能執行一個 -
aop:after
最終通知:無論切入點方法是否正常執行它都會在其後面執行 -
aop:around
環繞通知 ,它是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。Spring框架爲我們提供了一個接口:
ProceedingJoinPoint
。該接口有一個方法proceed()
,此方法就相當於明確調用切入點方法。 -
aop:pointcut
切入點表達式, id屬性用於指定表達式的唯一標識。expression屬性用於指定表達式內容
此標籤寫在aop:aspect
標籤內部只能當前切面使用。
它還可以寫在aop:aspect
外面,此時就變成了所有切面可用。
- 在
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("執行了保存");
// int i=1/0;
}
@Override
public void updateAccount(int i) {
System.out.println("執行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
/**
* 用於記錄日誌的工具類,它裏面提供了公共的代碼
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了");
}
/**
* 後置通知
*/
public void afterReturningPrintLog(){
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了");
}
/**
* 異常通知
*/
public void afterThrowingPrintLog(){
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了");
}
/**
* 最終通知
*/
public void afterPrintLog(){
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了");
}
/**
* 環繞通知
* 問題:
* 當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
* 分析:
* 通過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,而我們的代碼中沒有。
* 解決:
* Spring框架爲我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就相當於明確調用切入點方法。
* 該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供該接口的實現類供我們使用。
*
* spring中的環繞通知:
* 它是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的參數
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
rtValue = pjp.proceed(args);//明確調用業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置srping的Ioc,把service對象配置進來-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger類 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入點表達式 id屬性用於指定表達式的唯一標識。expression屬性用於指定表達式內容
此標籤寫在aop:aspect標籤內部只能當前切面使用。
它還可以寫在aop:aspect外面,此時就變成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入點方法執行之前執行
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
<!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠只能執行一個
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!-- 配置異常通知:在切入點方法執行產生異常之後執行。它和後置通知永遠只能執行一個
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
<!-- 配置環繞通知 詳細的註釋請看Logger類中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
3.使用註解配置AOP
/**
* 用於記錄日誌的工具類,它裏面提供了公共的代碼
*/
@Component("logger")
@Aspect//表示當前類是一個切面類
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
// @Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
}
/**
* 後置通知
*/
// @AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
}
/**
* 異常通知
*/
// @AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
}
/**
* 最終通知
*/
// @After("pt1()")
public void afterPrintLog(){
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
}
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的參數
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
rtValue = pjp.proceed(args);//明確調用業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring創建容器時要掃描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring開啓註解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>