目錄
一.AOP是什么
二.案例演示?
1.前置通知1.1 先準備接口
1.2然后再準備好實現類
1.3對我們的目標對象進行JavaBean配置?
1.4 編寫前置系統日志通知
1.5配置系統通知XML中的JavaBean
1.6 配置代理XML中的JavaBean
1.7 測試代碼開始測試
注意這里有一個報錯問題!!!
2. 后置通知2.1 先準備好后置通知的系統日志
2.2 配置后置系統通知的XML的JavaBean
?2.3 測試結果
3.環繞通知
3.2 環繞通知的系統日志
3.3 配置環繞通知的XML的JavaBean與前置通知和后置通知一致
3.4 測試結果
4.異常通知4.1 異常通知的系統日志和其他系統日志不同的是,方法名為固定的afterThrowing,不能修改
5.過濾通知5.1 直接在XML中配置JavaBean
四.總結aop是面向切面編程,普通程序由上而下正常執行,aop的程序執行是先執行到目標對象的目標方法中,如果連接點上由前置通知,則先執行前置通知再執行目標方法,最后如果目標方法有后置通知則最后執行后置通知代碼,不管是前置通知,后置通知,環繞通知,異常通知,過濾通知,代碼都是非業務核心代碼,如日志、事務的管理(開啟、提交、回滾) ? ? ? ??
一.AOP是什么
簡介:
面向切面編程(Aspect-Oriented Programming)是一種編程范式,它的主要目的是通過預編譯和運行期動態代理實現程序功能的橫切(cross-cutting)特性,如日志記錄、性能統計、事務監控等。它可以幫助開發者將這些原本分散在各個方法或類中的業務邏輯抽象出來,提高代碼復用性,降低耦合度
AOP(Aspect-Oriented Programming)是Spring框架的一個重要特性,它通過將橫切關注點(cross-cutting concerns)從核心業務邏輯中分離出來,以模塊化的方式在整個應用程序中重復使用。以下是關于AOP的簡介及其特點:AOP是一種編程范式,它通過將橫切關注點切割出來,將其模塊化,并將其應用于多個類和模塊,以提高代碼的重用性和可維護性。
橫切關注點是指與核心業務邏輯無關但存在于多個類或模塊中的非功能性需求,例如日志記錄、性能監控、事務管理等。
特點:模塊化:AOP允許將橫切關注點從核心業務邏輯中提取出來,形成獨立的切面(Aspect),使得關注點的邏輯可以獨立于各個模塊。
解耦:AOP通過解耦橫切關注點與核心業務邏輯,使得它們可以獨立演化和變化,提高了模塊之間的松耦合程度。
重用性:AOP允許將切面應用于多個類和模塊,從而實現了關注點的重用,避免了代碼的重復編寫。
可維護性:將橫切關注點抽象為切面后,使得代碼結構更清晰,易于理解和維護。
動態性:AOP可以在運行時動態地將切面應用到目標對象上,而不需要修改目標對象的源代碼,增強了系統的靈活性和可擴展性。
多樣性:Spring框架支持不同類型的切面編程,包括基于代理的AOP和基于字節碼增強的AOP。這樣可以選擇最適合應用程序需求的AOP實現方式。
在Spring框架中,AOP的實現采用了代理模式和動態代理技術。Spring提供了多種AOP的實現方式,包括基于XML配置的AOP、基于注解的AOP和基于純Java配置的AOP(JavaConfig)等,開發者可以根據具體需求選擇適合的方式來配置和使用AOP。
?
面向切面:
1.專業術語
①目標對象:
專業解釋:被通知(被代理)的對象通俗理解:在書店中,商品就是目標。每個商品都有自己的屬性(比如價格、名稱、庫存等)和行為(比如計算促銷價格、更新庫存等)。收銀員通過掃描商品的條形碼來與商品進行交互,調用商品的方法來獲取商品信息以及執行一些操作。商品本身即代表了目標
②連接點:
專業解釋:程序執行過程中明確的點,如方法的調用,或者異常的拋出通俗理解:在書店中,我們可以將顧客結賬的行為看作一個連接點
③通知:
專業解釋:在某個特定的連接點上執行的動作,同時Advice也是程序代碼的具體實現,例如一個實現日志記錄的代碼(通知有些書上也稱為處理)通俗理解:
前置通知(Before Advice):在切入點前執行的代碼,在讀者購買圖書之前,我們可以記錄讀者購買的圖書信息
后置通知(After Advice):在切入點后執行的代碼,在讀者購買圖書之后,我們可以更新圖書庫存
環繞通知(Around Advice):在切入點前后都執行的代碼,我們可以對讀者進行額外的安全檢查和記錄日志
異常通知(After-Throwing Advice):異常通知是在切入點發生異常時執行的額外功能代碼。假設當顧客購買商品的數量大于庫存數量時,就會發生異常。我們希望在顧客購買商品時檢查庫存,并在發生異常時執行異常通知,向顧客顯示錯誤信息并處理異常情況
過濾通知(After-Returning Advice):過濾通知是在切入點成功執行后執行的額外功能代碼。假設我們有一個特殊會員組,他們在購買商品時可以獲得額外的積分。我們可以使用過濾通知來篩選出這些特殊會員,并在成功購買后給他們添加積分
④代理:
專業解釋:將通知應用到目標對象后創建的對象(代理=目標+通知)通俗理解:在書店中,收銀員是一個代理角色。他們既代表顧客與商品交互,又代表書店執行一些額外的任務。當顧客帶著商品到收銀臺時,收銀員會掃描每個商品的條形碼,獲取商品信息并計算總價。這里,收銀員即充當了顧客與商品之間的代理角色,也充當了超市執行計算總價等額外任務的代理角色
⑤切入點:
專業解釋:多個連接點的集合,定義了通知應該應用到那些連接點 (也將Pointcut理解成一個條件 ,此條件決定了容器在什么情況下將通知和目標組合成代理返回給外部程序)
通俗理解:在書店場景中,我們可能希望在計算折扣方法之前或之后記錄日志和進行庫存管理。這些切入點決定了我們在代碼中操作的位置
⑥適配器:
專業解釋:適配器是一個中間組件,用于將面向切面編程框架與原始的業務邏輯代碼連接起來(適配器=通知(Advice)+切入點(Pointcut))通俗理解:在書店場景中,適配器可以將代理對象與書店的購買圖書業務邏輯連接起來,使得代理對象能夠在購買圖書的過程中添加額外的功能
2.代碼演示
? ? ? ? 在上面場景模擬的代碼中,我們能夠發現記錄日志的代碼基本相同,那么有沒有可能將這部分的代碼抽取出來進行封裝,統一進行維護呢?同時也可以將日志代碼和業務代碼完全分離,解耦合??那么我們便可以將業務方法中的非業務核心代碼(日志記錄)抽離出來形成一個橫切面,并且將這個橫切面封裝成一個對象,將所有的記錄日志的代碼寫到這個對象中,以實現與業務代碼的分離,這便是面向切面編程的思想
2.1將記錄日志的代碼進行封裝
?
三.案例演示?
1.前置通知
1.1 先準備接口
package com.lya.aop.biz;public interface IBookBiz {// 購書public boolean buy(String userName, String bookName, Double price);// 發表書評public void comment(String userName, String comments); }
1.2然后再準備好實現類
package com.lya.aop.biz.impl;import com.YU.aop.biz.IBookBiz; import com.YU.aop.exception.PriceException;public class BookBizImpl implements IBookBiz {public BookBizImpl() {super();}public boolean buy(String userName, String bookName, Double price) {// 通過控制臺的輸出方式模擬購書if (null == price || price <= 0) {throw new PriceException("book price exception");}System.out.println(userName + " buy " + bookName + ", spend " + price);return true;}public void comment(String userName, String comments) {// 通過控制臺的輸出方式模擬發表書評System.out.println(userName + " say:" + comments);}}
1.3對我們的目標對象進行JavaBean配置?
<!--目標對象-->
? ? <bean class="com.lya.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
1.4 編寫前置系統日志通知
package com.lya.aop.advice;import java.lang.reflect.Method; import java.util.Arrays;import org.springframework.aop.MethodBeforeAdvice;/*** 買書、評論前加系統日志* @author YU**/ public class MyMethodBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { //?? ??? ?在這里,可以獲取到目標類的全路徑及方法及方法參數,然后就可以將他們寫到日志表里去String target = arg2.getClass().getName();String methodName = arg0.getName();String args = Arrays.toString(arg1);System.out.println("【前置通知:系統日志】:"+target+"."+methodName+"("+args+")被調用了");}}
1.5配置系統通知XML中的JavaBean
<!--通知-->
? ? <bean class="com.lya.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>
1.6 配置代理XML中的JavaBean
<!-- 代理--><bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"><!-- 配置目標對象 --><property name="target" ref="bookBiz"></property><!-- 配置代理接口,目標對象的接口 --><property name="proxyInterfaces"><value>com.YU.aop.biz.IBookBiz</value></property><property name="interceptorNames"><list><value>myMethodBeforeAdvice</value></list></property></bean>
1.7 測試代碼開始測試
package com.lya.util;import com.lya.biz.IBookBiz; import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author 程序猿-小李哥* @site www.xiaolige.com* @company 豬八戒有限集團* @create 2023-08-17-15:34*/ public class Demo {public static void main(String[] args) { // 今天所學: // 1.AOP的介紹:專心做事// 2專業術語 // 1.連接點 // 2.通知:前,后,環繞 // 3.目標 // 4.代理 // 代理=目標+通知// 3配置xml初始化Spring容器IOCClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring.xml");// 演示一:目標對象 // BookBizImpl bookBiz = context.getBean("bookTarget",BookBizImpl.class); // bookBiz.buy("曉東","欠你一夜",2000d); // bookBiz.comment("曉東","不看虧了,看了真爽啊!");// 演示二:前置通知 // 錯誤:類型強轉,Object proxy1 = context.getBean("proxy");System.out.println(proxy1.getClass()+"代理的類型"); // com.sun.proxy.$Proxy5代理的類型// 這里proxy==new bookbizimpl // BookBizImpl proxy = context.getBean("proxy",BookBizImpl.class); // proxy.buy("曉東","欠你一夜",2000d); // proxy.comment("曉東","不看虧了,看了真爽啊!");// 使用接口接收代理對象!!!因為代理對象實現了接口在xml中IBookBiz proxy = context.getBean("proxy",IBookBiz.class);proxy.buy("曉東","欠你一夜",2000d);proxy.comment("曉東","不看虧了,看了真爽啊!");} }
注意這里有一個報錯問題!!!
因為proxy代理已經實現了接口可以看作為一個實現類
使用接口接收代理對象!!!因為代理對象實現了接口在xml中
測試結果:
?由測試結果可得知,不僅獲取到了我們的參數,同時根據方法獲取到了我們的系統日志,也就是前置通知
2. 后置通知
2.1 先準備好后置通知的系統日志
package com.zking.aop.advice;import java.lang.reflect.Method; import java.util.Arrays;import org.springframework.aop.AfterReturningAdvice;/*** 買書返利* @author Administrator**/ public class MyAfterReturningAdvice implements AfterReturningAdvice {@Overridepublic void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {String target = arg3.getClass().getName();String methodName = arg1.getName();String args = Arrays.toString(arg2);System.out.println("【后置通知:買書返利】:"+target+"."+methodName+"("+args+")被調用了,"+"該方法被調用后的返回值為:"+arg0);}}
2.2 配置后置系統通知的XML的JavaBean
<!--后置通知-->
? ? <bean class="com.YU.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
并在前面已經配置好的代理接口中添加一個value值
?2.3 測試結果
?由測試結果我們可以得知,后置通知永遠都在方法執行后才會顯示通知,與前置通知不同的是每次前面的方法調用后都會返回一個參數
3.環繞通知
3.1 環繞通知就是前置通知和后置通知的結合,在實際應用開發中,我們一般不會單獨編寫前置通知和后置通知,單獨使用前置通知或者后置通知時,我們會使用環繞通知,將里面前置(后置)通知的功能注釋,以達到單獨使用的目的
3.2 環繞通知的系統日志
package com.lya.advice;import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation;import java.util.Arrays;/*** @author 程序猿-小李哥* @site www.xiaolige.com* @company 豬八戒有限集團* @create 2023-08-17-18:45** 環繞通知*/ public class AroundAdvice implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//獲取目標對象的執行方法String methodName=invocation.getMethod().getName();//獲取目標對象執行方法的參數Object[] params=invocation.getArguments();//獲取目標對象Object target = invocation.getThis();System.out.println("[環繞通知] "+target.getClass().getName()+"."+methodName+","+ "執行的參數:"+ Arrays.toString(params));Object returnValue = invocation.proceed(); //放行操作System.out.println("[環繞通知] 返回參數等于:"+returnValue);return returnValue;} }
3.3 配置環繞通知的XML的JavaBean與前置通知和后置通知一致
3.4 測試結果
由測試結果得知,環繞通知就是前置通知和后置通知的結合,優點就是不需要再多次去進行配置及編碼,所以就像我們前面所說在實際開發應用中我們一般都會選擇使用環繞通知
4.異常通知
4.1 異常通知的系統日志和其他系統日志不同的是,方法名為固定的afterThrowing,不能修改
package com.lya.advice;/*** @author 程序猿-小李哥* @site www.xiaolige.com* @company 豬八戒有限集團* @create 2023-08-17-18:56*/import org.springframework.aop.ThrowsAdvice;/*** 異常通知*/ public class ExceptionAdvice implements ThrowsAdvice {public void afterThrowing(PriceException e) {System.out.println("[異常通知] 價格異常,撤銷訂單!");} }
價格異常
package com.lya.advice;/*** @author 程序猿-小李哥* @site www.xiaolige.com* @company 豬八戒有限集團* @create 2023-08-17-19:04** 價格異常通知*/ public class PriceException extends RuntimeException {public PriceException() {super();}public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}public PriceException(String message, Throwable cause) {super(message, cause);}public PriceException(String message) {super(message);}public PriceException(Throwable cause) {super(cause);} }
4.2 在我們正常程序出問題沒有去配置異常通知時會出現報錯,并且不會執行后面的后置通知,如以下情況
4.3 異常處理配置和前面的配置相同
4.4 當我們配置好異常通知模塊時,程序出現異常時會上報日志進行提示
5.過濾通知
5.1 直接在XML中配置JavaBean
<!--過濾通知-->
? ? <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor">
? ? ? ? <property name="advice" ref="myAfterReturningAdvice"></property>
? ? ? ? <property name="pattern" value=".*buy"></property>
? ? </bean>
?
將圖中指出部分替換成過濾通知
?測試結果:
對比框中內容,在調用過buy方法后進行過濾,第二次調用時不再buy方法而是comment方法?
四.總結
aop是面向切面編程,普通程序由上而下正常執行,aop的程序執行是先執行到目標對象的目標方法中,如果連接點上由前置通知,則先執行前置通知再執行目標方法,最后如果目標方法有后置通知則最后執行后置通知代碼,不管是前置通知,后置通知,環繞通知,異常通知,過濾通知,代碼都是非業務核心代碼,如日志、事務的管理(開啟、提交、回滾) ? ? ? ??
?