一篇文章帶你詳解Spring的AOP
1、AOP 什么?
AOP(Aspect Oriented Programming),通常稱為面向切面編程。它利用一種稱為"橫切"的技術(shù),剖解開封裝的對象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護(hù)性。
什么是切面,什么是公共模塊,那么我們概念少說,直接通過一個(gè)實(shí)例來看看 AOP 到底是什么。
2、需求
現(xiàn)在有一張表 User,然后我們要在程序中實(shí)現(xiàn)對 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();
}
}測試:
@Test
public void testOne(){
MyTransaction transaction = new MyTransaction();
UserService userService = new UserServiceImpl();
//產(chǎn)生靜態(tài)代理對象
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):
①、代理對象的一個(gè)接口只服務(wù)于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進(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;
}
}測試:
@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)給我們生成代理對象。而且將 UserService 換成別的類也是可以的。
也就是做到了代理對象能夠代理多個(gè)目標(biāo)類,多個(gè)目標(biāo)方法。
注意:我們這里使用的是 JDK 動(dòng)態(tài)代理,要求是必須要實(shí)現(xiàn)接口。與之對應(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)對象target來創(chuàng)建新的代理對象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)對象獲得具體方法
<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 的值要和 對應(yīng)切面的類方法名稱相同 -->
<aop:before method="before" pointcut-ref="myPointCut"></aop:before>
<aop:after-returning method="after" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>測試:
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser(null);
}結(jié)果:

上面的配置我們在注釋中寫的很清楚了。這里我們重點(diǎn)講解一下:
①、切入點(diǎn)表達(dá)式,一個(gè)完整的方法表示如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 類修飾符 返回值 方法所在的包 方法名 方法拋出的異常
那么根據(jù)上面的對比,我們就很好理解:
execution(* com.ys.aop.*.*(..)) 選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
②、springAOP的具體加載步驟:
1、當(dāng)spring容器啟動(dòng)的時(shí)候,加載了spring的配置文件
2、為配置文件中的所有bean創(chuàng)建對象
3、spring容器會(huì)解析aop:config的配置
1、解析切入點(diǎn)表達(dá)式,用切入點(diǎn)表達(dá)式和納入spring容器中的bean做匹配,如果匹配成功,則會(huì)為該bean創(chuàng)建代理對象,代理對象的方法=目標(biāo)方法+通知,如果匹配不成功,不會(huì)創(chuàng)建代理對象
4、在客戶端利用context.getBean()獲取對象時(shí),如果該對象有代理對象,則返回代理對象;如果沒有,則返回目標(biāo)對象
說明:如果目標(biāo)類沒有實(shí)現(xiàn)接口,則spring容器會(huì)采用cglib的方式產(chǎn)生代理對象,如果實(shí)現(xiàn)了接口,則會(huì)采用jdk的方式
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java中CyclicBarrier的理解與應(yīng)用詳解
這篇文章主要介紹了Java中CyclicBarrier的理解與應(yīng)用詳解,CyclicBarrier類是JUC框架中的工具類,也是一個(gè)同步輔助裝置:允許多個(gè)線程去等待直到全部線程抵達(dá)了公共的柵欄點(diǎn),需要的朋友可以參考下2023-12-12
基于Cookie與Session的Servlet?API會(huì)話管理操作
這篇文章主要為大家介紹了基于Cookie與Session的Servlet?API會(huì)話管理操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)
這篇文章主要介紹了Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2023-06-06
Java Spring JdbcTemplate基本使用詳解
JDBC已經(jīng)能夠滿足大部分用戶最基本的需求,但是在使用JDBC時(shí),必須自己來管理數(shù)據(jù)庫資源如:獲取PreparedStatement,設(shè)置SQL語句參數(shù),關(guān)閉連接等步驟2021-10-10
Java數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級隊(duì)列(PriorityQueue)用法詳解
優(yōu)先級隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),操作的數(shù)據(jù)帶有優(yōu)先級,這種數(shù)據(jù)結(jié)構(gòu)就是優(yōu)先級隊(duì)列(PriorityQueue)。本文將詳細(xì)講講Java優(yōu)先級隊(duì)列的用法,感興趣的可以了解一下2022-07-07

