對於最近博主最近寫博客的興致大發,我也在思考:爲何而寫博客?在互聯網時代,不管你是牛人大咖,仍是小白菜鳥,都有發表本身見解的權利。不管你是對的仍是錯的,都會在這個平臺上找到答案。因此,我會盡量去寫本身感興趣的內容,不管正面或者負面的消息,都儘量回覆個人每一位讀者。即便本身只有一個讀者,也會堅持寫下去。有一個平臺,去表達本身,記錄本身的點滴,難道不是一種快樂嗎?一樣,瞭解技術是一個深刻和拓展的過程,須要一我的清晰嚴謹的邏輯思惟。有時候,寫博客更像是給本身作筆記,鞏固分散的知識!java
爲何會有面向切面編程(AOP)?咱們知道Java是一個面向對象(OOP)的語言,但它有一些弊端,好比當咱們須要爲多個不具備繼承關係的對象引入一個公共行爲,例如日誌,權限驗證,事務等功能時,只能在在每一個對象裏引用公共行爲,這樣作不便於維護,並且有大量重複代碼。AOP的出現彌補了OOP的這點不足。spring
爲了闡述清楚Spring AOP,咱們從將如下方面進行討論:數據庫
1.代理模式。express
2.靜態代理原理及實踐。編程
3.動態代理原理及實踐。架構
4.Spring AOP原理及實戰。框架
代理模式:爲其餘對象提供一種代理以控制對這個對象的訪問。這段話比較官方,但我更傾向於用本身的語言理解:好比A對象要作一件事情,在沒有代理前,本身來作,在對A代理後,由A的代理類B來作。代理實際上是在原實例先後加了一層處理,這也是AOP的初級輪廓。分佈式
靜態代理模式:靜態代理說白了就是在程序運行前就已經存在代理類的字節碼文件,代理類和原始類的關係在運行前就已經肯定。廢話很少說,咱們看一下代碼,爲了方便閱讀,博主把單獨的class文件合併到接口中,讀者能夠直接複製代碼運行:ide
package test.staticProxy; // 接口 public interface IUserDao { void save(); void find(); } //目標對象 class UserDao implements IUserDao{ @Override public void save() { System.out.println("模擬:保存用戶!"); } @Override public void find() { System.out.println("模擬:查詢用戶"); } } /** 靜態代理 特色: 1. 目標對象必需要實現接口 2. 代理對象,要實現與目標對象同樣的接口 */ class UserDaoProxy implements IUserDao{ // 代理對象,須要維護一個目標對象 private IUserDao target = new UserDao(); @Override public void save() { System.out.println("代理操做: 開啓事務..."); target.save(); // 執行目標對象的方法 System.out.println("代理操做:提交事務..."); } @Override public void find() { target.find(); } }
測試結果:函數
靜態代理雖然保證了業務類只需關注邏輯自己,代理對象的一個接口只服務於一種類型的對象,若是要代理的方法不少,勢必要爲每一種方法都進行代理。再者,若是增長一個方法,除了實現類須要實現這個方法外,全部的代理類也要實現此方法。增長了代碼的維護成本。那麼要如何解決呢?答案是使用動態代理。
動態代理模式:動態代理類的源碼是在程序運行期間經過JVM反射等機制動態生成,代理類和委託類的關係是運行時才肯定的。實例以下:
package test.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 接口 public interface IUserDao { void save(); void find(); } //目標對象 class UserDao implements IUserDao{ @Override public void save() { System.out.println("模擬: 保存用戶!"); } @Override public void find() { System.out.println("查詢"); } } /** * 動態代理: * 代理工廠,給多個目標對象生成代理對象! * */ class ProxyFactory { // 接收一個目標對象 private Object target; public ProxyFactory(Object target) { this.target = target; } // 返回對目標對象(target)代理後的對象(proxy) public Object getProxyInstance() { Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目標對象使用的類加載器 target.getClass().getInterfaces(), // 目標對象實現的全部接口 new InvocationHandler() { // 執行代理對象方法時候觸發 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取當前執行的方法的方法名 String methodName = method.getName(); // 方法返回值 Object result = null; if ("find".equals(methodName)) { // 直接調用目標對象方法 result = method.invoke(target, args); } else { System.out.println("開啓事務..."); // 執行目標對象方法 result = method.invoke(target, args); System.out.println("提交事務..."); } return result; } } ); return proxy; } }
測試結果以下:
在運行測試類中建立測試類對象代碼中
IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
實際上是JDK動態生成了一個類去實現接口,隱藏了這個過程:
class $jdkProxy implements IUserDao{}
使用jdk生成的動態代理的前提是目標類必須有實現的接口。但這裏又引入一個問題,若是某個類沒有實現接口,就不能使用JDK動態代理,因此Cglib代理就是解決這個問題的。
Cglib是以動態生成的子類繼承目標的方式實現,在運行期動態的在內存中構建一個子類,以下:
public class UserDao{} //Cglib是以動態生成的子類繼承目標的方式實現,程序執行時,隱藏了下面的過程 public class $Cglib_Proxy_class extends UserDao{}
Cglib使用的前提是目標類不能爲final修飾。由於final修飾的類不能被繼承。
如今,咱們能夠看看AOP的定義:面向切面編程,核心原理是使用動態代理模式在方法執行先後或出現異常時加入相關邏輯。
經過定義和前面代碼咱們能夠發現3點:
1.AOP是基於動態代理模式。
2.AOP是方法級別的。
3.AOP能夠分離業務代碼和關注點代碼(重複代碼),在執行業務代碼時,動態的注入關注點代碼。切面就是關注點代碼造成的類。
前文提到JDK代理和Cglib代理兩種動態代理,優秀的Spring框架把兩種方式在底層都集成了進去,咱們無需擔憂本身去實現動態生成代理。那麼,Spring是如何生成代理對象的?:
1.建立容器對象的時候,根據切入點表達式攔截的類,生成代理對象。
2.若是目標對象有實現接口,使用jdk代理。若是目標對象沒有實現接口,則使用Cglib代理。而後從容器獲取代理後的對象,在運行期植入"切面"類的方法。經過查看Spring源碼,咱們在DefaultAopProxyFactory類中,找到這樣一段話。
簡單的從字面意思看出,若是有接口,則使用Jdk代理,反之使用Cglib,這恰好印證了前文所闡述的內容。Spring AOP綜合兩種代理方式的使用前提有會以下結論:若是目標類沒有實現接口,且class爲final修飾的,則不能進行Spring AOP編程!
知道了原理,如今咱們將本身手動實現Spring的AOP:
package test.spring_aop_anno; import org.aspectj.lang.ProceedingJoinPoint; public interface IUserDao { void save(); } //用於測試Cglib動態代理 class OrderDao { public void save() { //int i =1/0;用於測試異常通知 System.out.println("保存訂單..."); } } //用於測試jdk動態代理 class UserDao implements IUserDao { public void save() { //int i =1/0;用於測試異常通知 System.out.println("保存用戶..."); } } //切面類 class TransactionAop { public void beginTransaction() { System.out.println("[前置通知] 開啓事務.."); } public void commit() { System.out.println("[後置通知] 提交事務.."); } public void afterReturing(){ System.out.println("[返回後通知]"); } public void afterThrowing(){ System.out.println("[異常通知]"); } public void arroud(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("[環繞前:]"); pjp.proceed(); // 執行目標方法 System.out.println("[環繞後:]"); } }
Spring的xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- dao實例加入容器 --> <bean id="userDao" class="test.spring_aop_anno.UserDao"></bean> <!-- dao實例加入容器 --> <bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean> <!-- 實例化切面類 --> <bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean> <!-- Aop相關配置 --> <aop:config> <!-- 切入點表達式定義 --> <aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/> <!-- 切面配置 --> <aop:aspect ref="transactionAop"> <!-- 【環繞通知】 --> <aop:around method="arroud" pointcut-ref="transactionPointcut"/> <!-- 【前置通知】 在目標方法以前執行 --> <aop:before method="beginTransaction" pointcut-ref="transactionPointcut" /> <!-- 【後置通知】 --> <aop:after method="commit" pointcut-ref="transactionPointcut"/> <!-- 【返回後通知】 --> <aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/> <!-- 異常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/> </aop:aspect> </aop:config> </beans>
切入點表達式不在這裏介紹。ref:Spring AOP 切入點表達式
代碼的測試結果以下:
到這裏,咱們已經所有介紹完Spring AOP,回到開篇的問題,咱們拿它作什麼?
1.Spring聲明式事務管理配置,博主的另外一篇文章:分佈式系統架構實戰demo:SSM+Dubbo
2.Controller層的參數校驗。ref:Spring AOP攔截Controller作參數校驗
3.使用Spring AOP實現MySQL數據庫讀寫分離案例分析
4.在執行方法前,判斷是否具備權限。
5.,對部分函數的調用進行日誌記錄。監控部分重要函數,若拋出指定的異常,能夠以短信或郵件方式通知相關人員。
6.信息過濾,頁面轉發等等功能,博主一我的的力量有限,只能列舉這麼多,歡迎評論區對文章作補充。
Spring AOP還能作什麼,實現什麼魔幻功能,就在於咱們每個平凡而又睿智的程序猿!