Spring核心概念:AOP面向切面編程


Spring核心概念<二>:AOP面向切面編程

概述

AOP和OOP的區別:面向對象編程將程序分解爲各個層次的對象,而面向切面編程將程序運行過程分解爲各個切面。可以理解爲oop是從靜態角度考慮程序結構的,而aop是從動態角度考慮程序結構的

AOP的來源:當有多個對象同時複用一段相同的代碼段時,傳統的使用方法調用的方式無法避免耦合,所以出現了aop實現不同調用方法與複用代碼塊之間的解耦合。Aop專門用於處理系統中分佈於各個模塊中的交叉關注點的問題,常常使用AOP來處理一些具有橫切性質的系統級服務,如事務管理,安全檢查,緩存,對象池管理

AOP的目標:在不修改源代碼的前提下,爲系統多個組件的多個方法添加某種通用的功能。

AOP分類:靜態aop(AspectJ),編譯時增強/動態aop(Spring AOP)運行時增強

基本概念

aop代理:aop框架創建的對象,簡單來說,代理就是對目標對象的增強。

織入(weaving):將增強處理添加到目標對象。根據使用框架的不同可以細分爲運行時增強和編譯時增強

AOP代理方法 = 增強處理 + 目標對象的方法

對於開發者來說,主要參與

  1. 定義普通業務組件

  2. 定義切入點,一個切入點可橫切多個頁面

  3. 定義增強處理

基於註解的“零配置”方式/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 切入點指示符

  1. execution匹配執行方法連接點

  2. within用於限定匹配特定類型的連接點

  3. this用於限定aop代理必須是制定類型的實例

  4. target用於限定目標對象必須是指定類型的實例

  5. args用於對連接點參數類型進行限制

step7 組合切入點 :&& || ! 分別表示與或非

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章