本文內容
- Spring AOP含義和目標
- AOP相關概念
- 宣告式AOP快速入門
- 編程式創建代理物件
Spring AOP含義和目標
OOP: Object-oriented Programming 面向物件編程,大家再熟悉不過了
AOP:Aspect-oriented Programming 面向切面編程
面向切面編程通過提供另一種思考程式結構的方式來補充面向物件編程,OOP 中模塊化的關鍵單元是類,而 AOP 中模塊化的單元是切面,
Spring 的關鍵組件之一是 AOP 框架,Spring IoC 容器不依賴 AOP,AOP 對 Spring IoC 的補充提供了非常強大的中間件解決方案,主要用于下面2方面:
- 提供宣告式服務,最重要的此類服務是宣告式事務管理,
- 讓用戶實作自定義切面,用 AOP 補充他們對 OOP 的使用,
Spring AOP 的能力和目標
Spring AOP 是用純 Java 實作的,不需要特殊的編譯程序, Spring AOP 不需要控制類加載器層次結構,因此適用于 servlet 容器或應用程式服務器,
Spring AOP 當前僅支持方法執行連接點(建議在 Spring bean 上執行方法),欄位攔截未實作,
Spring AOP 的 AOP 方法不同于大多數其他 AOP 框架,盡管 Spring AOP 非常強,其目的不是提供最完整的 AOP 實作,相反,其目的是提供 AOP 實作和 Spring IoC 之間的緊密集成,以幫助解決企業應用程式中的常見問題,因此,Spring Framework 的 AOP 功能通常與 Spring IoC 容器結合使用,切面是通過使用普通的 bean 定義語法來配置的(盡管這允許強大的“自動代理”功能),這是與其他 AOP 實作的關鍵區別,
Spring AOP 從不努力與 AspectJ 競爭以提供全面的 AOP 解決方案,Spring AOP 等基于代理的框架和 AspectJ 等成熟框架都很有價值,它們是互補的,而不是競爭的,Spring 將 Spring AOP 和 IoC 與 AspectJ 無縫集成,以在一致的基于 Spring 的應用程式架構中實作 AOP 的所有使用,此集成不會影響 Spring AOP API 或 AOP Alliance API, Spring AOP 保持向后兼容,
AOP相關概念
先了解一下核心 AOP 概念和術語,方便后面深入使用,
切面 Aspect
跨多個類的關注點的模塊化,事務管理是企業 Java 應用程式中橫切關注點的一個很好的例子,
連接點 Join point
程式執行程序中的一個點,例如方法的執行或例外的處理,在 Spring AOP 中,一個連接點總是代表一個方法執行,
通知 Advice
切面在特定連接點采取的操作,不同型別如前置通知,環繞通知等, Spring將通知建模為攔截器,并在連接點周圍維護一系列攔截器,
切點 Pointcut
匹配連接點的謂詞, Advice 與切入點運算式相關聯,并在與切入點匹配的任何連接點處運行(例如執行具有特定名稱的方法),切入點運算式匹配的連接點的概念是 AOP 的核心,Spring 默認使用 AspectJ 切入點運算式語言,
引介 Introduction
在類上宣告其他方法或欄位, Spring AOP 允許向任何被增強的物件引入新介面和相應的實作,例如,可以使用 Introduction 使 bean 實作 IsModified 介面,以簡化快取,
目標物件 Target object
由一個或多個切面增強的物件,
代理 AOP proxy
由 AOP 框架創建的一個物件,用于實作切面邏輯如增強方法執行等,在 Spring Framework 中,AOP 代理是 JDK 動態代理或 CGLIB 代理,
織入 Weaving
將切面與其他應用程式型別或物件鏈接以創建增強物件,這可以在編譯時如使用 AspectJ 編譯器、加載時或運行時完成, Spring AOP 與其他純 Java AOP 框架一樣,在運行時執行編織,
結合網上的一張圖理解下,
切入點匹配的連接點的概念是 AOP 的關鍵,這將它與僅提供攔截的舊技術區分開來,切入點使增強Advice的目標獨立于面向物件的層次結構,如可以將提供宣告性事務管理的環繞通知應用到一組跨越多個物件(例如服務層中的所有業務操作)的方法,

快速入門
通過注解,宣告式的Spring AOP 的使用比較簡單,主要步驟如下:
- 通過 @EnableAspectJAutoProxy 啟用自動生成代理;
- 通過@Aspect 定義切面并注入到Spring容器中;
- 切面中可以通過@Pointcut定義切點;
- 切面通過@Before等通知注解來定義通知
- @Around 環繞通知
- @Before 前置通知
- @After 最終通知
- @AfterReturning 回傳通知
- @AfterThrowing 例外拋出后通知
- 從容器中獲取bean使用
以非入侵的方式記錄類方法開始執行的日志為例,完整看一個AOP的例子,
-
引入
aspectjweaver依賴<!--Aop需要的庫--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> -
啟用 @AspectJ 注解支持
@Configuration @ComponentScan @EnableAspectJAutoProxy() public class AppConfig {} -
定義目標物件
@Service public class UserService { public void add(String name) { System.out.println("UserService add " + name); } } -
宣告切面、切點和通知
/** * 切面定義 * 包含切點 通知 引入等 * * @author zfd * @version v1.0 * @date 2022/1/29 13:33 * @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列 */ @Component @Aspect // 切面 public class MyAspect { /** * 宣告切入點 這里運算式指:攔截UserService類所有方法執行 */ @Pointcut("execution(* com.crab.spring.aop.demo01.UserService.*(..))") public void pc(){} /** * 前置通知,指定切入點 * @param joinPoint */ @Before("pc()") public void before(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); System.out.println("我是前置通知!開始執行方法:" + signature.getMethod().getName()); // 方法執行前記錄日志 } } -
執行目標方法
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); IService bean = context.getBean(IService.class); System.out.println("bean的型別:" + bean.getClass()); bean.add("xxx"); context.close(); } -
觀察輸出結果
bean的型別:class com.sun.proxy.$Proxy19 我是前置通知!開始執行方法:add UserService add xxx從結果看成功攔截了,代理物件是通過JDK動態代理生成的,
-
類比不采用AOP的方式
上面的效果類似于下面的硬編碼的寫法
@Service public class UserService { public void add(String name) { System.out.println("我是前置通知!開始執行方法:" + "add"); System.out.println("UserService add " + name); } }
編程式創建代理
上面的快速入門式通過注解宣告式自動創建代理的,好處是簡單方便,缺點是使用者不清楚的創建程序和細節,為了深入了解AOP中代理式如何創建,我們看下編程式如何創建代理物件,主要類圖如下,

設計的介面或是基類是代理配置類AdvisedSupport、創建代理的工廠類AopProxyFactory和AopProxy ,總共4種手動創建代理物件的方式,
方式1:AdvisedSupport + AopProxyFactory 方式
這種方式最原始最基礎的,其它方式也是在此基礎上做封裝和簡化創建的,創建的代理物件主要考慮3個方面:
- 目標物件
- 代理方式的配置
- 如何創建代理物件
直接上案例,
/**
* 方式1
* 使用 AdvisedSupport + AopProxyFactory
*/
@Test
public void test1() {
// 1、目標物件
UserService target = new UserService();
// 2 代理配置資訊
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(target); // 目標物件
advisedSupport.addInterface(IService.class);// 代理的介面
advisedSupport.setProxyTargetClass(true);// 、強制cglib代理
advisedSupport.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知,開始執行方法: " + method.getName());
}
});
// 3 創建代理物件的工廠
DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
AopProxy aopProxy = proxyFactory.createAopProxy(advisedSupport);
// 4 獲取代理物件
Object proxy = aopProxy.getProxy();
// 5 查看代理的資訊
System.out.println("代理物件的型別:"+proxy.getClass());
System.out.println("代理物件的父類:"+proxy.getClass().getSuperclass());
System.out.println("代理物件實作的介面如下:");
for (Class<?> itf : proxy.getClass().getInterfaces()) {
System.out.println(itf);
}
}
代碼注釋比較清晰,來看下輸出結果,
代理物件的型別:class com.crab.spring.aop.demo01.UserService$$EnhancerBySpringCGLIB$$87584fdb
代理物件的父類:class com.crab.spring.aop.demo01.UserService
代理物件實作的介面如下:
interface com.crab.spring.aop.demo01.IService
interface org.springframework.aop.SpringProxy
interface org.springframework.aop.framework.Advised
interface org.springframework.cglib.proxy.Factory
結果看:
- 強制采用了CGLIB代理類的方式
- 默認實作了3個額外的介面SpringProxy、 Advised、Factory,后面2篇AOP原始碼決議會分析如何來的,
方式2:ProxyFactory
原始的方式需要同時操作代理的配置和代理工廠創建類,相對還是比較繁雜的,ProxyFactory 中參考了AopProxyFactory,一定程度簡化了創建程序,直接上案例,
/**
* 方式2
* 使用 ProxyFactory 簡化, ProxyFactory中組合了AopProxyFactory
*/
@Test
public void test2() {
// 1、目標物件
UserService target = new UserService();
// 2 創建代理物件的工廠,同時代理配置資訊
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);// 目標物件
proxyFactory.addInterface(IService.class);// 實作介面
// 添加通知
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知,開始執行方法: " + method.getName());
}
});
// 3 獲取代理物件
Object proxy = proxyFactory.getProxy();
// 5 呼叫方法
IService service = (IService) proxy;
service.hello("xx");
}
代理資訊的配置可以直接通過ProxyFactory設定,看下結果,
前置通知,開始執行方法: hello
hello xx
方式3:AspectJProxyFactory
AspectJProxyFactory 可以結合@Aspect的宣告的切面來創建代理物件的,理解這種方式對理解@Aspect宣告式使用AOP的方式很有幫助,詳細見我們的單獨的原始碼分析的文章,
直接上案例,
切面定義,含切點和通知,
/**
* @author zfd
* @version v1.0
* @date 2022/2/6 17:08
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
@Aspect
public class MyAspect {
/**
* 宣告切入點 這里運算式指:攔截UserService類所有方法執行
*/
@Pointcut("execution(* com.crab.spring.aop.demo01.UserService.*(..))")
public void pc(){}
/**
* 前置通知,指定切入點
* @param joinPoint
*/
@Before("pc()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("我是前置通知!開始執行方法:" + signature.getMethod().getName());
}
}
使用如下
/**
* 方式3 使用AspectProxyFactory結合@Aspect切面方式
*/
@Test
public void test3(){
// 1、目標物件
UserService target = new UserService();
// 2 創建代理物件的工廠,同時代理配置資訊
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.setInterfaces(IService.class);
// 設定切面 含通知和切點
proxyFactory.addAspect(MyAspect.class);
// 3 創建代理物件
IService proxy = proxyFactory.getProxy();
// 4 執行目標方法
proxy.hello("xx");
}
結果如下
前置通知: execution(void com.crab.spring.aop.demo02.IService.hello(String))
hello xx
方式4:ProxyFactoryBean
ProxyFactoryBean用來在spring環境中給指定的bean創建代理物件,用到的不是太多,了解即可,直接上案例,
不同于前面的方式,這種方式的目標物件和通知的設定方式是通過指定容器中的bean名稱來設定的,
package com.crab.spring.aop.demo01;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
/**
* ProxyFactoryBean 方式創建代理
* @author zfd
* @version v1.0
* @date 2022/2/6 17:20
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
@Configuration
public class AopProxyFactoryBeanConfig {
// 1 注冊目標物件
@Bean("userService")
public UserService userService() {
return new UserService();
}
// 2 注冊通知
@Bean("beforeAdvice")
public MethodBeforeAdvice beforeAdvice() {
return new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知: " + method);
}
};
}
// 3 注冊ProxyFactoryBean
@Bean("userServiceProxy")
public ProxyFactoryBean userServiceProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
// 設定目標物件的bean名稱
proxyFactoryBean.setTargetName("userService");
// 設定攔截器的bean名稱
proxyFactoryBean.setInterceptorNames("beforeAdvice");
// 代理方式
// proxyFactoryBean.setProxyTargetClass(true);
return proxyFactoryBean;
}
}
測驗程式和結果
/**
* 方式4 使用ProxyFactoryBean
*/
@Test
public void test04() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AopProxyFactoryBeanConfig.class);
// 面向介面,支持Jdk或是CGLIB
IService userService = (IService) context.getBean("userServiceProxy");
// 面向類,只支持CGLIB, proxyFactoryBean.setProxyTargetClass(true)
// UserService userService = context.getBean("userServiceProxy", UserService.class);
userService.hello("xxxx");
}
// 結果
前置通知: public abstract void com.crab.spring.aop.demo01.IService.hello(java.lang.String)
hello xxxx
總結
本文主要是介紹了Spring AOP 的相關概念、宣告式AOP的入門使用,以及編程式創建代理的4種方式,
本篇原始碼地址:https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-aop/src/main/java/com/crab/spring/aop/demo01
知識分享,轉載請注明出處,學無先后,達者為先!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/436313.html
標籤:Java
