[TOC]
### AOP
#### 什么是AOP
Aspect Oriented Programming:面向切面編程,作用簡單來說就是在不改變原類代碼的前提下,對類中的功能進行增強或者添加新的功能。
AOP在我們開發過程中使用頻率非常的高,比如我們要在多個地方重用一段代碼的功能,這時我們可以選擇的方式很多,比如直接代碼拷貝,也可以將代碼封裝成類或方法,使用時調用。但是問題是這種方式對代碼來說有著很強的侵入性,對于程序員來說,將重復的東西拷來拷去也是一件麻煩事。而AOP可以很好的解決這類問題,在AOP中我們可以指定對一類方法進行指定需要增強的功能。比如我們在系統中記錄數據修改的日志,每個對數據修改的方法都要記錄,但是其實完全是一樣的方法,使用AOP能大大增加開發效率。
#### AOP的一些概念
通知(advice):通知定義了一個切面在什么時候需要完成什么樣的功能,通知和切點組成切面。
切點(pointCut):切點定義了切面需要作用在什么地方。
切面(Aspect):是通知和切點的組合,表示在指定的時間點什對指點的地方進行一些額外的操作。
鏈接點(join points):連接點表示可以被選擇用來增強的位置,連接點是一組集合,在程序運行中的整個周期中都存在。
織入(Weaving):在不改變原類代碼的前提下,對功能進行增強。

#### 關于AOP的簡單分析
##### 通知(advice)
通知定義了一個切面在什么時候需要完成什么樣的功能,很明顯advice的實現不是由框架來完成,而是由用戶創建好advice然后注冊到框架中,讓框架在適當的時候使用它。這里我們需要考慮幾個問題。
用戶創建好的advice框架怎么感知?框架如何對用戶注冊的不同的advice進行隔離?
這個問題很簡單,大多數人都明白,這就類似于Java中的JDBC,Java提供一套公共的接口,各個數據庫廠商實現Java提供的接口來完成對數據庫的操作。我們這里也提供一套用于AOP的接口,用戶在使用時對接口進行實現即可。
advice的時機有哪些?需要提供哪些接口?
這里直接拿Spring中定義好的增強的時機。
* Before——在方法調用之前調用通知
* After——在方法完成之后調用通知,無論方法執行成功與否
* After-returning——在方法執行成功之后調用通知
* After-throwing——在方法拋出異常后進行通知
* Around——通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為
好了,我們可以使用一個接口來定義上面的處理方法,在用戶使用的時候實現方法即可,如下:

貌似差不多了,但是我們需要注意到,用戶在使用advice的使用,不可能說每次都是需要對上訴幾種方式同時進行增強,更多可能是只需要一種方式。但是如果只有一個接口的話就要求用戶每次都需要實現所有的方法,這樣顯的十分的不友好。
我們應該讓這些不同的方法對于用戶來說是可選,需要什么就實現哪一個。那么我們需要將每一個方法都對應一個接口嗎?不需要。上面的`after(...)`和`afterSuccess(...)`都是在方法執行之后實現,不同在于一個需要成功后的返回值而另一個不需要,這兩個可以作為一個實現由返回值區分。進行異常后的增強處理這要求對被執行的方法進行包裹住,捕獲異常。這就和環繞差不多了,兩者可以放一起。
類圖:

##### pointcut
advice基本就這樣了,下面就是pointcut了。說起切點,用過Spring中的AOP的肯定對切入點表達式比較了解了,在Spring中用戶通過切入點表達式來定義我們的增強功能作用在那一類方法上。這個切入點表達式十分的重要。對于我們的手寫AOP來說,也需要提供這樣的功能。當然表達式由用戶來寫,由我們的框架來解析用戶的表達式,然后對應到具體的方法上。
如何解析用戶定義的表達式?上面說到了,由一串字符來匹配一個或多個不同的目標,我們第一個反應肯定是正則表達式,很明顯這個功能使用正則是可以進行實現的。但實際上這樣的表達式還有很多。比如`AspectJ`,`Ant path`等。具體使用什么就自己決定了,這里我實現正則匹配這一種。
~~~
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
~~~
1. 如何找到我們要增強的方法呢?
當我們確定好有哪些類的哪些方法需要增強,后面就需要考慮我們如何獲取到這些方法(對方法增強肯定需要獲取到具體的方法)。
2. 有了表達式我們可以確定具體的類和方法,表達式只是定義了相對的路徑,如何根據相對路徑獲取Class文件地址?
對bean實例的增強是在初始化的時候完成的,初始化的時候判斷如果需要增強,則通過代理生成代理對象,在返回時由該代理對象代替原實例被注冊到容器中。
3. Class文件有了,怎么取到類中的方法?
在前面章節中我們獲取過方法,使用Class對象即可獲取所有的非私有方法。在實際調用被增強方法時,將該方法與所有的advice進行匹配,如果有匹配到advice,則執行相應的增強。當然我們并不需要每一次都需要遍歷獲取,為了效率可以對方法和增強的advice進行緩存。
##### Aspect/Advisor
我們有了增強功能的實現和確定了需要增強那些方法。到了現在我們就需要將拿到的方法進行增強了。
在運行過程中對已有的類或方法的功能進行增強同時又不改變原有類的代碼,這妥妥的代理模式嘛。如果不理解代理模式的可以看這個教程:[代理模式](https://link.juejin.im?target=http%3A%2F%2Fwww.runoob.com%2Fdesign-pattern%2Fproxy-pattern.html),代理模式可以在運行期間對方法進行增強,很好的實現我們的需求。
到現在,用戶要實現AOP需要提供什么呢?
用戶如果要實現AOP,首先必須提供一個Advice(通知)來增強功能,一個expression表達式來定義增強哪些方法,實際上還需要指定使用哪一個解析器來解析傳入的表達式(正則,AspectJ...)。如果單獨提供這些東西對用戶來說還是比較麻煩的,而框架的作用是幫用戶簡化開發過程中的流程,盡量的簡單化。所以在這里我們可以對用戶提供一個新的外觀(門面),讓用戶更加簡單的使用。這里其實是使用了[外觀模式](https://link.juejin.im?target=http%3A%2F%2Fwww.runoob.com%2Fdesign-pattern%2Ffacade-pattern.html)的思想。

當我們在注冊bean和調用方法時,對方法的增強會用到Advisor,所以我們還需要提供一個注冊和獲取Advisor的接口。

##### Weaving
現在我們有了切面,用戶也已經能夠比較簡單的來定義如何使用切面,最重要的一步到了,那就是我們應該如何對需要增強的類進行增強呢?什么時候進行增強?
上面已經說過了對類和方法進行增強就使用代理模式來增強。那么我們作為框架該在什么什么時候來增強呢?
這里有兩種時機。一是在啟動容器初始化bean的時候就進行增強,然后容器中存放的不是bean的實例,而是bean的代理實例。二是在每一次使用bean的時候判斷一次是否需要增強,需要就對其增強,然后返回bean的代理實例。這兩種方法很明顯第一種比較友好,只是讓容器的啟動時間稍微長了一點,而第二種在運行時判斷,會使得用戶的體驗變差。
在初始化bean的那個過程來增強?會不會存在問題?
根據之前的介紹,我們的框架初始化bean是在BeanFactory中進行,還包括bean的實例化,參數注入以及將bean放入容器中等。很明顯對bean的增強應該是在bean實例化完成并在還沒有放進容器中的時候。那么也就是在BeanFactory的doGetBean方法中了。這里有一個小問題在于,doGetBean方法做的事情已經夠多了,繼續往里加入代碼無疑會使得代碼大爆炸,很難維護也不易擴展。為了解決這個問題這里我們可以使用[觀察者模式](https://link.juejin.im?target=http%3A%2F%2Fwww.runoob.com%2Fdesign-pattern%2Fobserver-pattern.html)來解決這一問題,將doGetBean方法中每一個過程都作為一個觀察者存在,當我們需要添加功能是既可以添加一個觀察者然后注入,這樣不會對已有代碼做出改變。
定義一個觀察者的接口:

這里我們暫時只定義了aop應用的觀察者,其他的比如實例化,參數注入后面慢慢加。
BeanPostProcessor是在BeanFactory中對bean進行操作時觸發,我們也應該在BeanFactory中加入BeanPostProcessor的列表和注冊BeanPostProcessor的方法。

在這里的觀察者模式的應用中,BeanFactory充當subject角色,BeanPostProcessor則充當observer的角色,BeanFactory監聽BeanPostProcessor,我們可以將功能抽出為一個BeanPostProcessor,將其注冊到BeanFactory中,這樣既不會使得BeanFactory中代碼過多,同時也比較容易做到了功能的解耦,假設我們不需要某一個功能,那么直接接觸綁定即可而不需要任何其他操作。在這里我們只實現了Aop功能的注冊。

假設我們要對其他功能也抽為一個觀察者,那么直接繼承BeanPostProcessor接口實現自己的功能然后注冊到BeanFactory中。
##### 功能實現分析
現在接口有了,我們現在需要考慮如何來實現功能了。那么我們現在梳理一下我們需要做什么。
1. 在進行bean創建的時候,需要判斷該bean是否需要被增強,這個工作是由AopPostProcessor接口來做,判斷是否需要被增強和通過哪種方式來增強(JDK代理還是cglib代理)。如果需要增強則創建代理對象,注冊到容器是則使用該代理對象。
2. 在1中說到需要創建代理對象,那么我們也就需要提供代理的實現,目前代理主要是通過JDK代理和cglib代理模式,兩者的主要區別在去JDK代理模式必須要求類實現了接口,而cglib則不需要。
3. 在實際對實例增強方法調用時,框架需要對該方法的增強方法進行調用,如何進行調用以及存在多個增強方法是如何來調用。
現在我們對以上的問題分別分析解決。
##### 代理實現
代理的實現就是常規的實現,我們提供對外創建代理實例的方法和執行方法的處理。

JDKDynamicProxy和CglibDynamicProxy共同實現了AopProxy接口,除此之外要實現代理JDKDynamicProxy還需實現InvocationHandler接口,CglibDynamicProxy還需實現MethodInterceptor接口。
可能有朋友注意到了,在創建代理的類中都有一個BeanFactory的變量,之所以會用到這一個類型的變量是因為當方法運行時匹配到advice增強時能從BeanFactory中獲取Advice實例。而Advisor中并沒有存Advice的實例,存儲的是實例名(beanName)。但是問題在于這個變量的值我們如何獲取,對于一般的bean我們可以從容器中獲取,而BeanFactory本身就是容器,當然不可能再從容器中獲取。我們首先梳理下獲取變量值的方法:
1. 通過依賴注入從容器中獲取,這里不合適。
2. 直接創建一個新的值,這里需要的是容器中的實例,重新創建新的值肯定沒了,如果再按照原流程走一次創建一模一樣的值無疑是一種愚蠢的做法,這里也不合適。
3. 傳參,如果方法的調用流程可以追溯到該變量整個流程,可以通過傳參的方式傳遞
4. Spring中的做法,和3差不多,也是我們平時用的比較多的方法。提供一系列接口,接口唯一的作用就是用于傳遞變量的值,并且接口中也只有一個唯一的Set方法。

提供一個Aware父接口和一系列的子接口,比如BeanFactoryAware ,ApplicationContextAware用于將這些值放到需要的地方。如果那個類需要用到Spring容器的變量值,則直接實現xxxAware接口即可。Spring的做法是在某一個過程中檢測有哪些類實現了Aware接口,然后將值塞進去。
這里我們的準備工作都已經差不多了,后面就是開始將定義好的接口中的功能實現了。

##### 如果存在多個不同類型的增強方法時如何調用
由于在增強過程中,對于同一個方法可能有多個增強方法,比如多個環繞增強,多個后置增強等。通常情況下我們是通過一個for循環將所有方法執行,這樣的:

但是這里的問題在于,這中間的任何一個環繞方法都會執行一次原方法(被增強的方法),比如在環繞增強中的實現是這樣的:
~~~
//before working
//invoke 被加強的方法執行
//after working
~~~
這樣如果還是一個for循環執行的話就會導致一個方法被多次執行,所以for循環的方法肯定是不行的。我們需要的是一種類似于遞歸調用的方式嵌套執行,這樣的:

前面的方法執行一部分進入另一個方法,依次進入然后按照反順序結束方法,這樣只需把我們需要加強的方法放在最深層次來執行就可以保證只執行依次了。而[責任鏈模式](https://link.juejin.im?target=http%3A%2F%2Fwww.runoob.com%2Fdesign-pattern%2Fchain-of-responsibility-pattern.html)可以很好的做到這一點。
調用流程的具體實現:
~~~
public class AopAdviceChain {
private Method nextMethod;
private Method method;
private Object target;
private Object[] args;
private Object proxy;
private List<Advice> advices;
//通知的索引 記錄執行到第多少個advice
private int index = 0;
public AopAdviceChain(Method method, Object target, Object[] args, Object proxy, List<Advice> advices) {
try {
//對nextMethod初始化 確保調用正常進行
nextMethod = AopAdviceChain.class.getMethod("invoke", null);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
this.method = method;
this.target = target;
this.args = args;
this.proxy = proxy;
this.advices = advices;
}
public Object invoke() throws InvocationTargetException, IllegalAccessException {
if(index < this.advices.size()){
Advice advice = this.advices.get(index++);
if(advice instanceof BeforeAdvice){
//前置增強
((BeforeAdvice) advice).before(method, args, target);
}else if(advice instanceof AroundAdvice){
//環繞增強
return ((AroundAdvice) advice).around(nextMethod, null, this);
} else if(advice instanceof AfterAdvice){
//后置增強
//如果是后置增強需要先取到返回值
Object res = this.invoke();
((AfterAdvice) advice).after(method, args, target, res);
//后置增強后返回 否則會多執行一次
return res;
}
return this.invoke();
}else {
return method.invoke(target, args);
}
}
}
~~~
在代碼中可以看到,如果是前置增強則直接調用,而如果是環繞或者后置增強,則都不會立刻執行當前的增強方法,而是類似遞歸調用一樣,進行下一個執行。這樣就能保證被增強的方法不會被多次執行,同時對方法增強的順序也不會亂。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊