文章目錄
- 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):通過連接配接點我們可以知道切入點方法對象的很多資訊。
二、通知的介紹
通知類型就是想要加的代碼(校驗、日志等) 是在對象方法的前面還是後面執行的類型,這就是通知類型。
(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 隻有擷取切入點對象方法資訊的一些功能,不能幫助我們進行環繞寫代碼
能夠擷取對象方法參數、方法簽名等資訊
(2) ProceedingJoinPoint 作為環繞通知 的方法參數
ProceedingJoinPoint對象是JoinPoint的子接口,該對象隻用在@Around的切面方法中,
添加了
Object proceed() throws Throwable //執行目标方法
Object proceed(Object[] var1) throws Throwable //傳入的新的參數去執行目标方法
兩個方法.
調用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注解的方式,前置通知、後置通知、環繞通知的執行順序
環繞前和環繞後被 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配置就不一樣了