天天看點

Spring 架構學習(八)——AOP 的認識與使用

文章目錄

  • ​​Spring 架構學習(八)——AOP的認識與使用​​
  • ​​一、Spring AOP 常用概念及鋪墊​​
  • ​​二、通知的介紹​​
  • ​​(1)before 前置通知​​
  • ​​(2)after 後置通知​​
  • ​​(3)around 環繞通知​​
  • ​​(4)afterReturning 傳回後通知​​
  • ​​(5)afterThrowing 抛出異常後通知​​
  • ​​三、切點表達式說明​​
  • ​​(1)作用​​
  • ​​(2)格式​​
  • ​​(3)通配符介紹​​
  • ​​四、連接配接點及環繞方法參數使用​​
  • ​​(1) JoinPoint 作為環繞通知 的方法參數​​
  • ​​(2) ProceedingJoinPoint 作為環繞通知 的方法參數​​
  • ​​(3) 環繞通知的寫法​​
  • ​​(4) 在執行方法的時候修改方法參數​​
  • ​​五、Spring-AOP 準備工作​​
  • ​​(1)加入aspect織入包​​
  • ​​(2)加入aop限制以及開啟aop注解支援​​
  • ​​六、Spring中如何使用AOP​​
  • ​​(1)xml配置使用AOP​​
  • ​​(2)注解開發使用AOP​​

Spring 架構學習(八)——AOP的認識與使用

一、Spring AOP 常用概念及鋪墊

先說幾個常用的概念以及鋪墊的知識

橫切點:就是我們要給方法前加一個列印日志的功能,或者先校驗等等,這些功能

切面(Aspect):将橫切關注點進行子產品化的一個類

通知(Adviser):就是切面裡面具體的方法,用來切入到具體位置的方法裡面

切入點(PointCut):一個業務類中的具體方法,就是把通知切入到這個方法裡面

連接配接點(JoinPoint):通過連接配接點我們可以知道切入點方法對象的很多資訊。

Spring 架構學習(八)——AOP 的認識與使用

二、通知的介紹

通知類型就是想要加的代碼(校驗、日志等) 是在對象方法的前面還是後面執行的類型,這就是通知類型。

(1)before 前置通知

在方法執行前執行

如果方法出現了異常,不會影響前置通知的執行

應用:通常應用在執行方法前各種校驗

(2)after 後置通知

在方法執行完畢之後執行

無論方法是否出現異常終止都會執行

應用:清理現場,關閉、釋放資源

(3)around 環繞通知

方法執行前後分别執行

如果方法中出現異常終止,那麼末尾的通知就不執行了

應用:各個方面,結合了前置和後置的優點,還可以拿到對象方法的各種參數及資訊

(4)afterReturning 傳回後通知

方法正常傳回後執行

如果方法抛出異常,那麼無法執行

應用:正常結果資料處理

(5)afterThrowing 抛出異常後通知

方法抛出異常後執行

如果方法沒有抛出異常,無法執行

應用:抛出異常後對資訊進行包裝

三、切點表達式說明

(1)作用

用來比對具體切入點(方法)的具體位置

(2)格式

execution(切點表達式)

execution([修飾符] 傳回類型 包名.類名.方法名(參數類型))

方法通路修飾符可以進行省略,但是後面的東西必須得有

(3)通配符介紹

  • *(一個星号): 可以比對任意 傳回值、包名、類名、方法名、參數類型
  • …(兩個點):表示任意多個層級、任意多個參數

具體使用

我們想要把切點定義到 com.bit.service 包下的 UserService 類中的 void add(int a) 方法

有很多種寫法

1.每一部分都寫的具體

execution("void com.bit.service.UserService.add(int)")      

2.完全使用通配符

execution("* *..service.*(..)")
  //* 任意傳回類型
  //* 任意一個包
  // .. 包後面跟多層
  // * 類名
  // * 方法名
  //(..)任意多個任意參數      
execution("* *..*.*(..)")
  //* 任意傳回類型
  //* 任意一個包
  // .. 包後面跟多層
  // * 類名
  // * 方法名
  //(..)任意多個任意參數      
execution("* *..*(..)")
  //* 任意傳回類型
  //* 任意一個包
  // .. 包後面跟多層包+類名(接口名)
  // * 方法名
  //(..)任意多個任意參數      

四、連接配接點及環繞方法參數使用

前置通知,寫一個方法,在切入點之前執行通知即可

後置通知,寫一個方法,在切入點之前執行通知即可

那麼環繞通知的代碼如何進行環繞呢?

這裡就要用到 連接配接點(JoinPoint) 的一些使用了

(1) JoinPoint 作為環繞通知 的方法參數

JoinPoint對象封裝了SpringAop中切面方法的資訊,在切面方法中添加JoinPoint參數,就可以擷取到封裝了該方法資訊的JoinPoint對象.

JoinPoint 隻有擷取切入點對象方法資訊的一些功能,不能幫助我們進行環繞寫代碼

Spring 架構學習(八)——AOP 的認識與使用

能夠擷取對象方法參數、方法簽名等資訊

(2) ProceedingJoinPoint 作為環繞通知 的方法參數

ProceedingJoinPoint對象是JoinPoint的子接口,該對象隻用在@Around的切面方法中,

添加了

Object proceed() throws Throwable //執行目标方法

Object proceed(Object[] var1) throws Throwable //傳入的新的參數去執行目标方法

兩個方法.

Spring 架構學習(八)——AOP 的認識與使用

調用proceed方法,相當于執行切入點方法

(3) 環繞通知的寫法

public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around=====方法環繞前====");
        joinPoint.proceed();  // 執行方法
        System.out.println("Around======方法環繞後=====");
    }      

(4) 在執行方法的時候修改方法參數

1、使用 Object [] args 接收原方法傳入的參數

Object[] args = joinPoint.getArgs();      

2、對數組中的參數進行修改

for (int i = 0; i <args.length ; i++) {
        args[i] = (Integer)args[i]+10;
  }      

3、執行proceed帶參數的方法,将修改過的object[] args進行傳入

joinPoint.proceed(args);  // 執行方法      

五、Spring-AOP 準備工作

(1)加入aspect織入包

在編寫SpringAOP面向切面程式設計時,需要導入一個aspectjweaver.jar的包,它的主要作用是負責解析切入點表達式。

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.9.1</version>
        </dependency>      

(2)加入aop限制以及開啟aop注解支援

可以在xml使用<aop:config 回車自動生成,下面是aop限制已經加上了的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--開啟aop注解的支援 -->
    
    <aop:aspectj-autoproxy />
    
    <!--在aop:config 中配置切面、切點、以及各種通知 或者使用注解配置即可-->

    <context:annotation-config/>

    <context:component-scan base-package="com.*"/>
</beans>      

六、Spring中如何使用AOP

(1)xml配置使用AOP

(1)先寫一個業務的接口

package com.service;

public interface UserService {
    void add();
};      

(2)給這個接口建立建立一個實作類,類中的方法作為切入點。

package com.service;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("執行了add方法");
    }
}      

(3)自定義一個類作為切面,在裡面實作一些方法作為具體的通知。

package com.config;
import org.aspectj.lang.ProceedingJoinPoint;

public class DiyAspect {
    // 該類作為切面,所有橫切關注點的集合的一個子產品

     public void before(){
         System.out.println("Before=====方法執行前====");
     }

    public void after(){
        System.out.println("After=====方法執行後====");
    }

    public void around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("Around=====方法環繞前====");
        pj.proceed();  // 執行方法
        System.out.println("Around======方法環繞後=====");
    }
    
}      

(4)通過xml配置定義切點、切面、通知,以及使得切面裡的通知綁定到切點上。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="diyAspect" class="com.config.DiyAspect"/>
    
    <!--開啟aop注解的支援 -->
    <aop:aspectj-autoproxy />

    <!--在aop:config 中配置切面、切點、以及各種通知 或者使用注解配置即可-->
    <aop:config >
        <aop:pointcut id="pointcut" expression="execution(* *..service.*.*(..))"/>
        <aop:aspect id="diy" ref="diyAspect">
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

<!--    開啟元件的注解支援-->
    <context:annotation-config/>
<!--    開啟包路徑下的元件掃描-->
    <context:component-scan base-package="com.*"/>
</beans>      

(5)進行測試,執行add方法,檢視通知增強是否綁定切入點成功

package com.controller;

import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public static void main(String[] args) {
        ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
    }
}      

(6)我們可以确定,通過xml注解的方式,前置通知、後置通知、環繞通知的執行順序

Spring 架構學習(八)——AOP 的認識與使用

環繞前和環繞後被 before after包裹着

(2)注解開發使用AOP

(1)使用注解 @Aspect 将自定義類作為切面,@PointCut 定義切點的位置,@Before / @After 定義通知作用到切點上

package com.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DiyAspect {
    // 該類作為切面,所有橫切關注點的集合的一個子產品

    // 可以使用@PointCut 定義一個切點,在之後隻需要調用這個方法即可,不需要重複的寫切點表達式
    @Pointcut("execution( * com.service.UserServiceImpl.add(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
     public void before(){
         System.out.println("Before=====方法執行前====");
     }

     @After("pointCut()")
    public void after(){
        System.out.println("After=====方法執行後====");
    }

    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around=====方法環繞前====");
        joinPoint.proceed();  // 執行方法
        System.out.println("Around======方法環繞後=====");
    }

}      

(2)xml檔案中可以完全不同寫aop:config 的配置内容,但是必須加上aop限制和aop注解支援

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--開啟aop注解的支援 -->
    <aop:aspectj-autoproxy />

<!--    開啟元件的注解支援-->
    <context:annotation-config/>
<!--    開啟包路徑下的元件掃描-->
    <context:component-scan base-package="com.*"/>

</beans>      

(3)進行測試,執行add方法,檢視通知增強是否綁定切入點成功

package com.controller;

import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public static void main(String[] args) {
        ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
    }
}      

(4) 我們可以确定,通過注解開發的方式,前置通知、後置通知、環繞通知的執行順序和之前xml配置就不一樣了