<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] ### AOP #### 什么是AOP Aspect Oriented Programming:面向切面編程,作用簡單來說就是在不改變原類代碼的前提下,對類中的功能進行增強或者添加新的功能。 AOP在我們開發過程中使用頻率非常的高,比如我們要在多個地方重用一段代碼的功能,這時我們可以選擇的方式很多,比如直接代碼拷貝,也可以將代碼封裝成類或方法,使用時調用。但是問題是這種方式對代碼來說有著很強的侵入性,對于程序員來說,將重復的東西拷來拷去也是一件麻煩事。而AOP可以很好的解決這類問題,在AOP中我們可以指定對一類方法進行指定需要增強的功能。比如我們在系統中記錄數據修改的日志,每個對數據修改的方法都要記錄,但是其實完全是一樣的方法,使用AOP能大大增加開發效率。 #### AOP的一些概念 通知(advice):通知定義了一個切面在什么時候需要完成什么樣的功能,通知和切點組成切面。 切點(pointCut):切點定義了切面需要作用在什么地方。 切面(Aspect):是通知和切點的組合,表示在指定的時間點什對指點的地方進行一些額外的操作。 鏈接點(join points):連接點表示可以被選擇用來增強的位置,連接點是一組集合,在程序運行中的整個周期中都存在。 織入(Weaving):在不改變原類代碼的前提下,對功能進行增強。 ![](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e585c70d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) #### 關于AOP的簡單分析 ##### 通知(advice) 通知定義了一個切面在什么時候需要完成什么樣的功能,很明顯advice的實現不是由框架來完成,而是由用戶創建好advice然后注冊到框架中,讓框架在適當的時候使用它。這里我們需要考慮幾個問題。 用戶創建好的advice框架怎么感知?框架如何對用戶注冊的不同的advice進行隔離? 這個問題很簡單,大多數人都明白,這就類似于Java中的JDBC,Java提供一套公共的接口,各個數據庫廠商實現Java提供的接口來完成對數據庫的操作。我們這里也提供一套用于AOP的接口,用戶在使用時對接口進行實現即可。 advice的時機有哪些?需要提供哪些接口? 這里直接拿Spring中定義好的增強的時機。 * Before——在方法調用之前調用通知 * After——在方法完成之后調用通知,無論方法執行成功與否 * After-returning——在方法執行成功之后調用通知 * After-throwing——在方法拋出異常后進行通知 * Around——通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為 好了,我們可以使用一個接口來定義上面的處理方法,在用戶使用的時候實現方法即可,如下: ![](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e598643d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 貌似差不多了,但是我們需要注意到,用戶在使用advice的使用,不可能說每次都是需要對上訴幾種方式同時進行增強,更多可能是只需要一種方式。但是如果只有一個接口的話就要求用戶每次都需要實現所有的方法,這樣顯的十分的不友好。 我們應該讓這些不同的方法對于用戶來說是可選,需要什么就實現哪一個。那么我們需要將每一個方法都對應一個接口嗎?不需要。上面的`after(...)`和`afterSuccess(...)`都是在方法執行之后實現,不同在于一個需要成功后的返回值而另一個不需要,這兩個可以作為一個實現由返回值區分。進行異常后的增強處理這要求對被執行的方法進行包裹住,捕獲異常。這就和環繞差不多了,兩者可以放一起。 類圖: ![](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e57915cd?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) ##### 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)的思想。 ![](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e63237ae?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 當我們在注冊bean和調用方法時,對方法的增強會用到Advisor,所以我們還需要提供一個注冊和獲取Advisor的接口。 ![AdvisorRegistry](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e6802611?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) ##### 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方法中每一個過程都作為一個觀察者存在,當我們需要添加功能是既可以添加一個觀察者然后注入,這樣不會對已有代碼做出改變。 定義一個觀察者的接口: ![BeanPostProcessor](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b5e7f45572?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 這里我們暫時只定義了aop應用的觀察者,其他的比如實例化,參數注入后面慢慢加。 BeanPostProcessor是在BeanFactory中對bean進行操作時觸發,我們也應該在BeanFactory中加入BeanPostProcessor的列表和注冊BeanPostProcessor的方法。 ![BeanFactory](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b60b8635c5?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 在這里的觀察者模式的應用中,BeanFactory充當subject角色,BeanPostProcessor則充當observer的角色,BeanFactory監聽BeanPostProcessor,我們可以將功能抽出為一個BeanPostProcessor,將其注冊到BeanFactory中,這樣既不會使得BeanFactory中代碼過多,同時也比較容易做到了功能的解耦,假設我們不需要某一個功能,那么直接接觸綁定即可而不需要任何其他操作。在這里我們只實現了Aop功能的注冊。 ![image](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b6175ec357?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 假設我們要對其他功能也抽為一個觀察者,那么直接繼承BeanPostProcessor接口實現自己的功能然后注冊到BeanFactory中。 ##### 功能實現分析 現在接口有了,我們現在需要考慮如何來實現功能了。那么我們現在梳理一下我們需要做什么。 1. 在進行bean創建的時候,需要判斷該bean是否需要被增強,這個工作是由AopPostProcessor接口來做,判斷是否需要被增強和通過哪種方式來增強(JDK代理還是cglib代理)。如果需要增強則創建代理對象,注冊到容器是則使用該代理對象。 2. 在1中說到需要創建代理對象,那么我們也就需要提供代理的實現,目前代理主要是通過JDK代理和cglib代理模式,兩者的主要區別在去JDK代理模式必須要求類實現了接口,而cglib則不需要。 3. 在實際對實例增強方法調用時,框架需要對該方法的增強方法進行調用,如何進行調用以及存在多個增強方法是如何來調用。 現在我們對以上的問題分別分析解決。 ##### 代理實現 代理的實現就是常規的實現,我們提供對外創建代理實例的方法和執行方法的處理。 ![AopProxy](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b61aeb1fb7?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) JDKDynamicProxy和CglibDynamicProxy共同實現了AopProxy接口,除此之外要實現代理JDKDynamicProxy還需實現InvocationHandler接口,CglibDynamicProxy還需實現MethodInterceptor接口。 可能有朋友注意到了,在創建代理的類中都有一個BeanFactory的變量,之所以會用到這一個類型的變量是因為當方法運行時匹配到advice增強時能從BeanFactory中獲取Advice實例。而Advisor中并沒有存Advice的實例,存儲的是實例名(beanName)。但是問題在于這個變量的值我們如何獲取,對于一般的bean我們可以從容器中獲取,而BeanFactory本身就是容器,當然不可能再從容器中獲取。我們首先梳理下獲取變量值的方法: 1. 通過依賴注入從容器中獲取,這里不合適。 2. 直接創建一個新的值,這里需要的是容器中的實例,重新創建新的值肯定沒了,如果再按照原流程走一次創建一模一樣的值無疑是一種愚蠢的做法,這里也不合適。 3. 傳參,如果方法的調用流程可以追溯到該變量整個流程,可以通過傳參的方式傳遞 4. Spring中的做法,和3差不多,也是我們平時用的比較多的方法。提供一系列接口,接口唯一的作用就是用于傳遞變量的值,并且接口中也只有一個唯一的Set方法。 ![Aware](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b630672020?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 提供一個Aware父接口和一系列的子接口,比如BeanFactoryAware ,ApplicationContextAware用于將這些值放到需要的地方。如果那個類需要用到Spring容器的變量值,則直接實現xxxAware接口即可。Spring的做法是在某一個過程中檢測有哪些類實現了Aware接口,然后將值塞進去。 這里我們的準備工作都已經差不多了,后面就是開始將定義好的接口中的功能實現了。 ![image](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b63311b286?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) ##### 如果存在多個不同類型的增強方法時如何調用 由于在增強過程中,對于同一個方法可能有多個增強方法,比如多個環繞增強,多個后置增強等。通常情況下我們是通過一個for循環將所有方法執行,這樣的: ![執行順序](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b6356e5811?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 但是這里的問題在于,這中間的任何一個環繞方法都會執行一次原方法(被增強的方法),比如在環繞增強中的實現是這樣的: ~~~ //before working //invoke 被加強的方法執行 //after working ~~~ 這樣如果還是一個for循環執行的話就會導致一個方法被多次執行,所以for循環的方法肯定是不行的。我們需要的是一種類似于遞歸調用的方式嵌套執行,這樣的: ![遞歸順序](https://user-gold-cdn.xitu.io/2018/12/21/167ce5b637568113?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 前面的方法執行一部分進入另一個方法,依次進入然后按照反順序結束方法,這樣只需把我們需要加強的方法放在最深層次來執行就可以保證只執行依次了。而[責任鏈模式](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); } } } ~~~ 在代碼中可以看到,如果是前置增強則直接調用,而如果是環繞或者后置增強,則都不會立刻執行當前的增強方法,而是類似遞歸調用一樣,進行下一個執行。這樣就能保證被增強的方法不會被多次執行,同時對方法增強的順序也不會亂。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看