一篇文章帶你詳解Spring的AOP
1、AOP 什么?
AOP(Aspect Oriented Programming),通常稱為面向切面編程。它利用一種稱為"橫切"的技術(shù),剖解開封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡(jiǎn)單說就是那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護(hù)性。
什么是切面,什么是公共模塊,那么我們概念少說,直接通過一個(gè)實(shí)例來看看 AOP 到底是什么。
2、需求
現(xiàn)在有一張表 User,然后我們要在程序中實(shí)現(xiàn)對(duì) User表的增加和刪除操作。
要求:增加和刪除操作都必須要開啟事務(wù),操作完成之后要提交事務(wù)。
User.java
package com.ys.aop.one;
public class User {
private int uid;
private String uname;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
}3、解決辦法1:使用靜態(tài)代理
第一步:創(chuàng)建 UserService 接口
package com.ys.aop.one;
public interface UserService {
//添加 user
public void addUser(User user);
//刪除 user
public void deleteUser(int uid);
}第二步:創(chuàng)建 UserService的實(shí)現(xiàn)類
package com.ys.aop.one;
public class UserServiceImpl implements UserService{
@Override
public void addUser(User user) {
System.out.println("增加 User");
}
@Override
public void deleteUser(int uid) {
System.out.println("刪除 User");
}
}第三步:創(chuàng)建事務(wù)類 MyTransaction
package com.ys.aop.one;
public class MyTransaction {
//開啟事務(wù)
public void before(){
System.out.println("開啟事務(wù)");
}
//提交事務(wù)
public void after(){
System.out.println("提交事務(wù)");
}
}第四步:創(chuàng)建代理類 ProxyUser.java
package com.ys.aop.one;
public class ProxyUser implements UserService{
//真實(shí)類
private UserService userService;
//事務(wù)類
private MyTransaction transaction;
//使用構(gòu)造函數(shù)實(shí)例化
public ProxyUser(UserService userService,MyTransaction transaction){
this.userService = userService;
this.transaction = transaction;
}
@Override
public void addUser(User user) {
transaction.before();
userService.addUser(user);
transaction.after();
}
@Override
public void deleteUser(int uid) {
transaction.before();
userService.deleteUser(uid);
transaction.after();
}
}測(cè)試:
@Test
public void testOne(){
MyTransaction transaction = new MyTransaction();
UserService userService = new UserServiceImpl();
//產(chǎn)生靜態(tài)代理對(duì)象
ProxyUser proxy = new ProxyUser(userService, transaction);
proxy.addUser(null);
proxy.deleteUser(0);
}結(jié)果:

這是一個(gè)很基礎(chǔ)的靜態(tài)代理,業(yè)務(wù)類UserServiceImpl 只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)的重用性,這也是代理類的優(yōu)點(diǎn),沒什么好說的。我們主要說說這樣寫的缺點(diǎn):
①、代理對(duì)象的一個(gè)接口只服務(wù)于一種類型的對(duì)象,如果要代理的方法很多,勢(shì)必要為每一種方法都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無法勝任了。
②、如果接口增加一個(gè)方法,比如 UserService 增加修改 updateUser()方法,則除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。
4、解決辦法2:使用JDK動(dòng)態(tài)代理
動(dòng)態(tài)代理就不要自己手動(dòng)生成代理類了,我們?nèi)サ?ProxyUser.java 類,增加一個(gè)ObjectInterceptor.java 類
package com.ys.aop.two;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.ys.aop.one.MyTransaction;
public class ObjectInterceptor implements InvocationHandler{
//目標(biāo)類
private Object target;
//切面類(這里指事務(wù)類)
private MyTransaction transaction;
//通過構(gòu)造器賦值
public ObjectInterceptor(Object target,MyTransaction transaction){
this.target = target;
this.transaction = transaction;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//開啟事務(wù)
this.transaction.before();
//調(diào)用目標(biāo)類方法
method.invoke(this.target, args);
//提交事務(wù)
this.transaction.after();
return null;
}
}測(cè)試:
@Test
public void testOne(){
//目標(biāo)類
Object target = new UserServiceImpl();
//事務(wù)類
MyTransaction transaction = new MyTransaction();
ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
/**
* 三個(gè)參數(shù)的含義:
* 1、目標(biāo)類的類加載器
* 2、目標(biāo)類所有實(shí)現(xiàn)的接口
* 3、攔截器
*/
UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), proxyObject);
userService.addUser(null);
}結(jié)果:

那么使用動(dòng)態(tài)代理來完成這個(gè)需求就很好了,后期在 UserService 中增加業(yè)務(wù)方法,都不用更改代碼就能自動(dòng)給我們生成代理對(duì)象。而且將 UserService 換成別的類也是可以的。
也就是做到了代理對(duì)象能夠代理多個(gè)目標(biāo)類,多個(gè)目標(biāo)方法。
注意:我們這里使用的是 JDK 動(dòng)態(tài)代理,要求是必須要實(shí)現(xiàn)接口。與之對(duì)應(yīng)的另外一種動(dòng)態(tài)代理實(shí)現(xiàn)模式 Cglib,則不需要,我們這里就不講解 cglib 的實(shí)現(xiàn)方式了。
不管是哪種方式實(shí)現(xiàn)動(dòng)態(tài)代理。本章的主角:AOP 實(shí)現(xiàn)原理也是動(dòng)態(tài)代理
5、AOP 關(guān)鍵術(shù)語
1.target:目標(biāo)類,需要被代理的類。例如:UserService
2.Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些可能被攔截到的方法。例如:所有的方法
3.PointCut 切入點(diǎn):已經(jīng)被增強(qiáng)的連接點(diǎn)。例如:addUser()
4.advice 通知/增強(qiáng),增強(qiáng)代碼。例如:after、before
5. Weaving(織入):是指把增強(qiáng)advice應(yīng)用到目標(biāo)對(duì)象target來創(chuàng)建新的代理對(duì)象proxy的過程.
6.proxy 代理類:通知+切入點(diǎn)
7. Aspect(切面): 是切入點(diǎn)pointcut和通知advice的結(jié)合
具體可以根據(jù)下面這張圖來理解:

6、AOP 的通知類型
Spring按照通知Advice在目標(biāo)類方法的連接點(diǎn)位置,可以分為5類
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 在目標(biāo)方法執(zhí)行前實(shí)施增強(qiáng),比如上面例子的 before()方法
- 后置通知 org.springframework.aop.AfterReturningAdvice
- 在目標(biāo)方法執(zhí)行后實(shí)施增強(qiáng),比如上面例子的 after()方法
- 環(huán)繞通知org.aopalliance.intercept.MethodInterceptor
- 在目標(biāo)方法執(zhí)行前后實(shí)施增強(qiáng)
- 異常拋出通知 org.springframework.aop.ThrowsAdvice
- 在方法拋出異常后實(shí)施增強(qiáng)
- 引介通知 org.springframework.aop.IntroductionInterceptor
- 在目標(biāo)類中添加一些新的方法和屬性
7、使用 Spring AOP 解決上面的需求
我們只需要在Spring 的配置文件 applicationContext.xml 進(jìn)行如下配置:
<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">
<!--1、 創(chuàng)建目標(biāo)類 -->
<bean id="userService" class="com.ys.aop.UserServiceImpl"></bean>
<!--2、創(chuàng)建切面類(通知) -->
<bean id="transaction" class="com.ys.aop.one.MyTransaction"></bean>
<!--3、aop編程
3.1 導(dǎo)入命名空間
3.2 使用 <aop:config>進(jìn)行配置
proxy-target-class="true" 聲明時(shí)使用cglib代理
如果不聲明,Spring 會(huì)自動(dòng)選擇cglib代理還是JDK動(dòng)態(tài)代理
<aop:pointcut> 切入點(diǎn) ,從目標(biāo)對(duì)象獲得具體方法
<aop:advisor> 特殊的切面,只有一個(gè)通知 和 一個(gè)切入點(diǎn)
advice-ref 通知引用
pointcut-ref 切入點(diǎn)引用
3.3 切入點(diǎn)表達(dá)式
execution(* com.ys.aop.*.*(..))
選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
-->
<aop:config>
<!-- 切入點(diǎn)表達(dá)式 -->
<aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/>
<aop:aspect ref="transaction">
<!-- 配置前置通知,注意 method 的值要和 對(duì)應(yīng)切面的類方法名稱相同 -->
<aop:before method="before" pointcut-ref="myPointCut"></aop:before>
<aop:after-returning method="after" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>測(cè)試:
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser(null);
}結(jié)果:

上面的配置我們?cè)谧⑨屩袑懙暮芮宄恕_@里我們重點(diǎn)講解一下:
①、切入點(diǎn)表達(dá)式,一個(gè)完整的方法表示如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 類修飾符 返回值 方法所在的包 方法名 方法拋出的異常
那么根據(jù)上面的對(duì)比,我們就很好理解:
execution(* com.ys.aop.*.*(..)) 選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
②、springAOP的具體加載步驟:
1、當(dāng)spring容器啟動(dòng)的時(shí)候,加載了spring的配置文件
2、為配置文件中的所有bean創(chuàng)建對(duì)象
3、spring容器會(huì)解析aop:config的配置
1、解析切入點(diǎn)表達(dá)式,用切入點(diǎn)表達(dá)式和納入spring容器中的bean做匹配,如果匹配成功,則會(huì)為該bean創(chuàng)建代理對(duì)象,代理對(duì)象的方法=目標(biāo)方法+通知,如果匹配不成功,不會(huì)創(chuàng)建代理對(duì)象
4、在客戶端利用context.getBean()獲取對(duì)象時(shí),如果該對(duì)象有代理對(duì)象,則返回代理對(duì)象;如果沒有,則返回目標(biāo)對(duì)象
說明:如果目標(biāo)類沒有實(shí)現(xiàn)接口,則spring容器會(huì)采用cglib的方式產(chǎn)生代理對(duì)象,如果實(shí)現(xiàn)了接口,則會(huì)采用jdk的方式
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
springmvc 分頁查詢的簡(jiǎn)單實(shí)現(xiàn)示例代碼
我們?cè)陂_發(fā)項(xiàng)目中很多項(xiàng)目都用到列表分頁功能,本篇介紹了springmvc 分頁查詢的簡(jiǎn)單實(shí)現(xiàn)示例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
利用java實(shí)現(xiàn)中獎(jiǎng)概率詳情
這篇文章主要介紹了利用java實(shí)現(xiàn)中獎(jiǎng)概率詳情,根據(jù)概率將獎(jiǎng)品劃分區(qū)間,每個(gè)區(qū)間代表一個(gè)獎(jiǎng)品,然后抽取???隨機(jī)數(shù)??,反查落在那個(gè)區(qū)間上,即為所抽取的獎(jiǎng)品,需要的朋友可以參考一下2022-07-07
SpringBoot如何使用MyBatis-Plus實(shí)現(xiàn)高效的數(shù)據(jù)訪問層
在開發(fā) Spring Boot 應(yīng)用時(shí),數(shù)據(jù)訪問是不可或缺的部分,本文將詳細(xì)介紹如何在 Spring Boot 中使用 MyBatis-Plus,并結(jié)合具體代碼示例來講解它的使用方法和常見配置,希望對(duì)大家有一定的幫助2025-04-04
VS?Code中運(yùn)行Java?SpringBoot的項(xiàng)目詳細(xì)步驟
這篇文章主要介紹了VS?Code中運(yùn)行Java?SpringBoot項(xiàng)目的相關(guān)資料,文中涵蓋了安裝必要的擴(kuò)展、配置環(huán)境、創(chuàng)建或?qū)腠?xiàng)目、配置調(diào)試環(huán)境、運(yùn)行和調(diào)試項(xiàng)目、使用Spring?Boot?Actuator以及配置任務(wù)自動(dòng)化等步驟,需要的朋友可以參考下2024-12-12
介紹下Java Spring的核心接口,容器中Bean的實(shí)例化
這篇文章主要介紹了Spring核心接口,容器中bean的實(shí)例化過程解析及完整代碼示例,簡(jiǎn)單分析實(shí)例化bean過程并且分享了相關(guān)實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2021-09-09
springsecurity輕松實(shí)現(xiàn)角色權(quán)限的示例代碼
這篇文章主要介紹了springsecurity輕松實(shí)現(xiàn)角色權(quán)限的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Lombok 的@StandardException注解解析
@StandardException 是一個(gè)實(shí)驗(yàn)性的注解,添加到 Project Lombok 的 v__1.18.22 版本中,在本教程中,我們將使用 Lombok 的 @StandardException 注解自動(dòng)生成異常類型類的構(gòu)造函數(shù),需要的朋友可以參考下2023-05-05

