一篇文章帶你詳解Spring的AOP
1、AOP 什么?
AOP(Aspect Oriented Programming),通常稱為面向切面編程。它利用一種稱為"橫切"的技術(shù),剖解開封裝的對(duì)象內(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)對(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)文章
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-08Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)
這篇文章主要介紹了Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2023-06-06Java Spring JdbcTemplate基本使用詳解
JDBC已經(jīng)能夠滿足大部分用戶最基本的需求,但是在使用JDBC時(shí),必須自己來管理數(shù)據(jù)庫資源如:獲取PreparedStatement,設(shè)置SQL語句參數(shù),關(guān)閉連接等步驟2021-10-10Java數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級(jí)隊(duì)列(PriorityQueue)用法詳解
優(yōu)先級(jí)隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),操作的數(shù)據(jù)帶有優(yōu)先級(jí),這種數(shù)據(jù)結(jié)構(gòu)就是優(yōu)先級(jí)隊(duì)列(PriorityQueue)。本文將詳細(xì)講講Java優(yōu)先級(jí)隊(duì)列的用法,感興趣的可以了解一下2022-07-07