目錄
1.AOP是什么???
2.案列:
3.spring的aop的專業術語
?4.代碼模擬
4.1 前置通知
??3.2.后置通知
??3.3.環繞通知
??3.4.異常通知
?3.5.過濾通知
1.AOP是什么???
- 面向切面編程(Aspect-Oriented Programming)是一種編程范式,它的主要目的是通過預編譯和運行期動態代理實現程序功能的橫切(cross-cutting)特性,如日志記錄、性能統計、事務監控等。它可以幫助開發者將這些原本分散在各個方法或類中的業務邏輯抽象出來,提高代碼復用性,降低耦合度
- 面向切面編程的核心思想是將程序分為兩個部分:切入點和切面。切入點(entry point)是程序執行過程中需要被攔截的代碼段,而切面(weaving)則是實現橫切功能的代碼段。在運行時,通過動態代理技術,將切入點的代碼交由切面織入,實現橫切的效果
- 面向切面編程是希望能夠將通用需求功能從不相關的類當中分離出來,能夠使得很多類共享一個行為,一旦發生變化,不必修改很多類,而只是修改這個行為即可
- AOP通過提供另一種思考程序結構的方式來補充了面向對象編程(OOP)。OOP中模塊化的基本單元是類(class),而AOP中模塊化的基本單元是切面(aspect)。可以這么理解,OOP是解決了縱向的代碼復用問題,AOP是解決了橫向的代碼復用問題.
2.案列:
場景模擬:這里我模擬的場景為線上書城系統
首先進行一個沒有使用Aop的模擬代碼展示:
實體類:Book:其中是書籍的屬性,行為等 Dao類:BookDao:其中是有關于書籍操作的代碼 業務邏輯層:BookBiz:...,BookBizImpl:... Web層:new 對象//增加 public int add(Book book) { b.add() }//修改 public int edit(Book book) { b.edit() }//刪除 public int del(Book book) { b.del() }//上架 public int up(Book book) { b.up() }//下架 public int down(Book book) { b.down() }
但是很多時候會出現這樣的情況:
①用戶購買了書籍,卻說自己沒有收到貨物...
②店家發出的貨物為空貨物,而其卻為了牟利,矢口否認...
針對這樣的情況,很多時候都沒有證據來證明到底是真是假,而在我們成熟的系統中,通常都會添加一個叫做‘日志記錄’的東西,它可以用來記錄和跟蹤系統、應用程序或事件的活動和狀態的過程,通俗來說就是使用這個系統的用戶的每一步操作都會被記錄下來,這樣就話就成為一個證據:那成熟的系統應該是怎么樣?
實體類:Book:其中是書籍的屬性,行為等 Dao類:BookDao:其中是有關于書籍操作的代碼 業務邏輯層:BookBiz:...,BookBizImpl:... Web層:new對象//增加 public int add(Book book) { b.add() }//修改 public int edit(Book book) { b.edit() }//刪除 public int del(Book book) { b.del() }//上架 public int up(Book book) { datetime=..//操作時的時間 username=...//操作的用戶名 args = ...//參數 logBiz.add(datetime,username,args);//將其都添加到日志中去b.up() }//下架 public int down(Book book) { datetime=..//操作時的時間 username=...//操作的用戶名 args = ...//參數 logBiz.add(datetime,username,args);//將其都添加到日志中去b.down() }
現在是在上架和下架中添加了日志記錄,如果要在其他的方法操作中,也添加日志記錄的話,那就需要將這一段代碼再重復幾次,這樣就有點麻煩,而且還改變了原有代碼的結構,如果需求發生改變,需要對打印的日志內容作出修改,那就必須修改用到了日志記錄方法中的所有相關代碼,如果是1000個方法呢?每次就需要手動去修改1000個方法中的代碼,對項目的維護成本就會很高這也不利于我們的系統維護。然后我們可以用到AOP可以幫助開發者將將原本分散在各個方法或類中的業務邏輯抽象出來,提高代碼復用性,降低耦合度,接下怎么提高代碼的復用性:
3.spring的aop的專業術語
? ?項目代碼但是從上往下依次執行,而現在加入了面向切面的思想,當我們的代碼執行到目標對象是,查看連接點是否有前置通知,先執行前置通知,再執行目標方法,如果沒有前置通知,那么就直接執行目標方法,最后看連接點上是否有后置通知,如果有,就再執行后置通知,如果沒有就執行完了。
- ?連接點(Joinpoint):程序執行過程中明確的點,如方法的調用,或者異常的拋出.
- ?目標(Target):被通知(被代理)的對象,就是完成具體的業務邏輯 ,比如書籍的增刪改查
- ?通知(Advice):在某個特定的連接點上執行的動作,同時Advice也是程序代碼的具體實現,例如一個實現日志記錄的代碼(通知有些書上也稱為處理) ?,完成切面編程,非業務核心代碼
- ?代理(Proxy):將通知應用到目標對象后創建的對象(代理=目標+通知), ?例子:外科醫生+護士只有代理對象才有AOP功能,而AOP的代碼是寫在通知的方法里面的
- 切入點(Pointcut):多個連接點的集合,定義了通知應該應用到那些連接點 , (也將Pointcut理解成一個條件 ,此條件決定了容器在什么情況下將通知和目標組合成代理返回給外部程序),比如給新增方法添加日志功能
- ?適配器(Advisor):適配器=通知(Advice)+切入點(Pointcut)
? ? ?注:目標對象只負責業務邏輯代碼
???? ? ? ? ? ?通知對象負責AOP代碼,這二個對象都沒有AOP的功能,只有代理對象才有。
?4.代碼模擬
4.1 前置通知
????????首先,我們先寫service接口和實現類進行模擬,在里面寫兩個方法
package com.sy.aop.biz;public interface IBookBiz {// 購書public boolean buy(String userName, String bookName, Double price);// 發表書評public void comment(String userName, String comments);
}
? ? ?然后,寫實現類,重新這兩個方法,并且做了一個價格的判斷
package com.sy.aop.biz.impl;import com.sy.aop.biz.IBookBiz;
import com.sy.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);}}
?接下來要寫上面價格判斷的異常?
package com.sy.aop.exception;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);}}
然后,我們先創建一個類,將類名.方法名,攜帶的參數,作為日志存儲到數據庫。
package com.sy.aop.advice;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;
import java.util.Arrays;/*** 買書、評論前加系統日志* @author shenyan**/
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+")被調用了");}}
?最后,進行一個配置
!--aop-->
<!-- 目標對象 -->
<bean class="com.sy.aop.biz.impl.BookBizImpl" id="bookBiz"></bean><!-- 通知 -->
<bean class="com.sy.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean><!-- 代理=目標+通知 --><bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"><property name="target" ref="bookBiz"></property><property name="proxyInterfaces"><list><value>com.sy.aop.biz.IBookBiz</value></list></property><property name="interceptorNames"><list><value>myMethodBeforeAdvice</value></list></property></bean>
前臺的一個驗證:
package com.sy.aop.demo;import com.sy.aop.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author 諶艷* @site www.shenyan.com* @create 2023-08-17 21:13*/
public class demo1 {public static void main (String [] args){ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-context.xml");IBookBiz bookBiz=(IBookBiz) context.getBean("bookBiz");bookBiz.buy("花花","天下掉下一個林妹妹",18.88);bookBiz.comment("嘿嘿","嘿嘿嘿真好看");}
}
結果為展示:
??3.2.后置通知
????????有了前面的鋪墊,直接再創建一個后置通知的類,比起前置通知,多了一個參數,就是返回參數
package com.sy.aop.advice;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;
import java.util.Arrays;/*** 買書、評論前加系統日志* @author shenyan**/
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+")被調用了");}}
接著,配置文件即可
?然后前臺看結果;
package com.sy.aop.demo;import com.sy.aop.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author 諶艷* @site www.shenyan.com* @create 2023-08-17 21:13*/
public class demo1 {public static void main (String [] args){ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml");
// IBookBiz bookBiz=(IBookBiz) context.getBean("bookBiz");IBookBiz bookBiz=(IBookBiz) context.getBean("bookProxy");bookBiz.buy("花花","天下掉下一個林妹妹",18.88);bookBiz.comment("嘿嘿","嘿嘿嘿真好看");}
}
??3.3.環繞通知
? ? ? ??結合了前置通知和后置通知,它兩個都有所以一般常用這個,
????????它只有一個參數,但是這一個參數相當于上面前置通知和后置通知的3,4個參數
package com.sy.aop.advice;import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;import java.util.Arrays;/*** 環繞通知* 包含了前置和后置通知* * @author shenyan**/
public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation arg0) throws Throwable {String target = arg0.getThis().getClass().getName();String methodName = arg0.getMethod().getName();String args = Arrays.toString(arg0.getArguments());System.out.println("【環繞通知調用前:】:"+target+"."+methodName+"("+args+")被調用了");
// arg0.proceed()就是目標對象的方法Object proceed = arg0.proceed();System.out.println("【環繞通知調用后:】:該方法被調用后的返回值為:"+proceed);return proceed;}}
? 接著就是配置文件
?然后前臺測試:
?結果展示:
??3.4.異常通知
? ??? ? 先建一個類,但是注意,這個異常通知的類,重寫的話,方法名字只能是這個,否則報錯
然后配置文件:
?然后在前臺測試:
結果展示:
?3.5.過濾通知
????????過濾通知就是那個適配器,它不需要再建一個類,直接再配置文件里面配置就可以了,需要正則判斷,這里舉例過濾的是后置通知
?結果展示:
??今天小編的分享就結束吶,生活總是需要不斷去學習新的知識,多想想然后再去實操,持之以恒,經驗和思維都會發生轉變,我們要保持謙虛學習和自信的態度,各位加油!