日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

自己动手实现的 Spring IOC 和 AOP - 下篇

發布時間:2025/3/21 javascript 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自己动手实现的 Spring IOC 和 AOP - 下篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 背景

本文承接上文,來繼續說說 IOC 和 AOP 的仿寫。在上文中,我實現了一個很簡單的 IOC 和 AOP 容器。上文實現的 IOC 和 AOP 功能很單一,且 IOC 和 AOP 兩個模塊沒有整合到一起。IOC 在加載 bean 過程中,AOP 不能對 bean 織入通知。在本文中,我們詳細說一下升級版 IOC 和 AOP。這個版本的實現包含了在上篇中所說的功能,這里再重述一下,如下:

  • 根據 xml 配置文件加載相關 bean
  • 對 BeanPostProcessor 類型的 bean 提供支持
  • 對 BeanFactoryAware 類型的 bean 提供支持
  • 實現了基于 JDK 動態代理的 AOP
  • 整合了 IOC 和 AOP,使得二者可很好的協同工作
  • 上面羅列了5個功能點,雖然看起來不多,但是對于新手來說,實現起來還是不很容易的。所以接下來,我將圍繞上面的功能點展開詳細的描述。如果大家有興趣,我還是很建議大家跟著寫一遍,因為很多時候能看懂,但是寫的卻不一定能寫出來。仿寫一遍能夠加深對 Spring IOC 和 AOP 原理的理解,多動手是有好處的。

    另外需要說明的是,黃億華前輩實現的?tiny-spring?項目時間節點是 2014.1,當時應該是參照 Spring 3.x 版本編寫的。部分類的設計思想可能會與現在最新穩定版 4.3.10 有一定的出入,由于我暫時沒有閱讀 Spring 源碼的計劃,所以這里不能告知大家?tiny-spring?哪些類與 Spring 最新的源碼有出入,見諒。

    好了,本章內容先介紹到這,接下來進入正文。

    ?2. IOC 的實現

    ?2.1 BeanFactory 的生命流程

    上面簡述了 toy-spring 項目的編碼背景,接下來,在本節中,我將向大家介紹 toy-spring 項目中 IOC 部分的實現原理。在詳細介紹 IOC 的實現原理前,這里先簡單說一下 BeanFactory 的生命流程:

  • BeanFactory 加載 Bean 配置文件,將讀到的 Bean 配置封裝成 BeanDefinition 對象
  • 將封裝好的 BeanDefinition 對象注冊到 BeanDefinition 容器中
  • 注冊 BeanPostProcessor 相關實現類到 BeanPostProcessor 容器中
  • BeanFactory 進入就緒狀態
  • 外部調用 BeanFactory 的 getBean(String name) 方法,BeanFactory 著手實例化相應的 bean
  • 重復步驟 3 和 4,直至程序退出,BeanFactory 被銷毀
  • 上面簡單羅列了 BeanFactory 的生命流程,也就是 IOC 容器的生命流程。接下來就來圍繞上面的流程展開討論。

    ?2.2 BeanDefinition 及其他一些類的介紹

    在詳細介紹 IOC 容器的工作原理前,這里先介紹一下實現 IOC 所用到的一些輔助類,包括BeanDefinition、BeanReference、PropertyValues、PropertyValue。這些類與接下來的 2.3 節 xml 的解析緊密相關。按照順序,先從 BeanDefinition 開始介紹。

    BeanDefinition,從字面意思上翻譯成中文就是 “Bean 的定義”。從翻譯結果中就可以猜出這個類的用途,即根據 Bean 配置信息生成相應的 Bean 詳情對象。舉個例子,如果把 Bean 比作是電腦 ?,那么 BeanDefinition 就是這臺電腦的配置清單。我們從外觀上無法看出這臺電腦里面都有哪些配置,也看不出電腦的性能咋樣。但是通過配置清單,我們就可了解這臺電腦的詳細配置。我們可以知道這臺電腦是不是用了牙膏廠的 CPU,BOOM 廠的固態硬盤等。透過配置清單,我們也就可大致評估出這臺電腦的性能。

    圖1 電腦和配置清單

    上面那個例子還是比較貼切的,但是只是個例子,和實際還是有差距的。那么在具體實現中,BeanDefinition 和 xml 是怎么對應的呢?答案在下面:

    圖2 根據 bean 配置生成 BeanDefinition

    看完上圖,我想大家對 BeanDefinition 的用途有了更進一步的認識。接下來我們來說說上圖中的 ref 對應的 BeanReference 對象。BeanReference 對象保存的是 bean 配置中 ref 屬性對應的值,在后續 BeanFactory 實例化 bean 時,會根據 BeanReference 保存的值去實例化 bean 所依賴的其他 bean。

    接下來說說 PropertyValues 和 PropertyValue 這兩個長的比較像的類,首先是PropertyValue。PropertyValue 中有兩個字段 name 和 value,用于記錄 bean 配置中的標簽的屬性值。然后是PropertyValues,PropertyValues 從字面意思上來看,是 PropertyValue 復數形式,在功能上等同于 List。那么為什么 Spring 不直接使用 List,而自己定義一個新類呢?答案是要獲得一定的控制權,看下面的代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class PropertyValues {private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();public void addPropertyValue(PropertyValue pv) {// 在這里可以對參數值 pv 做一些處理,如果直接使用 List,則就不行了this.propertyValueList.add(pv);}public List<PropertyValue> getPropertyValues() {return this.propertyValueList;}}

    好了,輔助類介紹完了,接下來我們繼續 BeanFactory 的生命流程探索。

    ?2.3 xml 的解析

    BeanFactory 初始化時,會根據傳入的 xml 配置文件路徑加載并解析配置文件。但是加載和解析 xml 配置文件這種臟活累活,BeanFactory 可不太愿意干,它只想高冷的管理容器中的 bean。于是 BeanFactory 將加載和解析配置文件的任務委托給專職人員 BeanDefinitionReader 的實現類 XmlBeanDefinitionReader 去做。那么 XmlBeanDefinitionReader 具體是怎么做的呢?XmlBeanDefinitionReader 做了如下幾件事情:

  • 將 xml 配置文件加載到內存中
  • 獲取根標簽下所有的標簽
  • 遍歷獲取到的標簽列表,并從標簽中讀取 id,class 屬性
  • 創建 BeanDefinition 對象,并將剛剛讀取到的 id,class 屬性值保存到對象中
  • 遍歷標簽下的標簽,從中讀取屬性值,并保持在 BeanDefinition 對象中
  • 將 <id, BeanDefinition> 鍵值對緩存在 Map 中,留作后用
  • 重復3、4、5、6步,直至解析結束
  • 上面的解析步驟并不復雜,實現起來也不難,就是解析 xml 而已,這里就不過多敘述了。

    ?2.4 注冊 BeanPostProcessor

    BeanPostProcessor 接口是 Spring 對外拓展的接口之一,其主要用途提供一個機會,讓開發人員能夠插手 bean 的實例化過程。通過實現這個接口,我們就可在 bean 實例化時,對bean 進行一些處理。比如,我們所熟悉的 AOP 就是在這里將切面邏輯織入相關 bean 中的。正是因為有了 BeanPostProcessor 接口作為橋梁,才使得 AOP 可以和 IOC 容器產生聯系。關于這一點,我將會在后續章節詳細說明。

    接下來說說 BeanFactory 是怎么注冊 BeanPostProcessor 相關實現類的。

    XmlBeanDefinitionReader 在完成解析工作后,BeanFactory 會將它解析得到的 <id, BeanDefinition> 鍵值對注冊到自己的 beanDefinitionMap 中。BeanFactory 注冊好 BeanDefinition 后,就立即開始注冊 BeanPostProcessor 相關實現類。這個過程比較簡單:

  • 根據 BeanDefinition 記錄的信息,尋找所有實現了 BeanPostProcessor 接口的類。
  • 實例化 BeanPostProcessor 接口的實現類
  • 將實例化好的對象放入 List中
  • 重復2、3步,直至所有的實現類完成注冊
  • 上面簡述了 BeanPostProcessor 接口的用途以及注冊的過程。BeanPostProcessor 是一個比較常用接口,相信大家都很熟悉了,這里就不過多敘述了。

    ?2.5 getBean 過程解析

    在完成了 xml 的解析、BeanDefinition 的注冊以及 BeanPostProcessor 的注冊過程后。BeanFactory 初始化的工作算是結束了,此時 BeanFactory 處于就緒狀態,等待外部程序的調用。

    外部程序一般都是通過調用 BeanFactory 的 getBean(String name) 方法來獲取容器中的 bean。BeanFactory 具有延遲實例化 bean 的特性,也就是等外部程序需要的時候,才實例化相關的 bean。這樣做的好處是比較顯而易見的,第一是提高了 BeanFactory 的初始化速度,第二是節省了內存資源。下面我們就來詳細說說 bean 的實例化過程:

    圖3 Spring bean實例化過程

    上圖是一個完整的 Spring bean 實例化過程圖。在我的仿寫項目中,沒有做的這么復雜,簡化了 bean 實例化的過程,如下:

    圖4 toy-spring bean實例化過程

    接下來我將按照簡化后的 bean 實例化過程介紹,如果想了解完整的 bean 實例化過程,可以參考我的另一篇文章:Spring bean的生命流程。簡化后的實例化流程如下:

  • 實例化 bean 對象,類似于 new XXObject()
  • 將配置文件中配置的屬性填充到剛剛創建的 bean 對象中
  • 檢查 bean 對象是否實現了 Aware 一類的接口,如果實現了則把相應的依賴設置到 bean 對象中。toy-spring 目前僅對 BeanFactoryAware 接口實現類提供了支持
  • 調用 BeanPostProcessor 前置處理方法,即 postProcessBeforeInitialization(Object bean, String beanName)
  • 調用 BeanPostProcessor 后置處理方法,即 postProcessAfterInitialization(Object bean, String beanName)
  • bean 對象處于就緒狀態,可以使用了
  • 上面 6 步流程并不復雜,源碼實現的也較為簡單,這里就不在貼代碼說明了。大家如果想了解細節,可以去 github 上下載?toy-spring?源碼閱讀。

    ?3. 實現 AOP

    ?3.1 AOP 原理

    AOP 是基于動態代理模式實現的,具體實現上可以基于 JDK 動態代理或者 Cglib 動態代理。其中 JDK 動態代理只能代理實現了接口的對象,而 Cglib 動態代理則無此限制。所以在為沒有實現接口的對象生成代理時,只能使用 Cglib。在 toy-spring 項目中,暫時只實現了基于 JDK 動態代理的代理對象生成器。

    關于 AOP 原理這里就不多說了,下面說說 toy-spring 中 AOP 的實現步驟。還是像上面一樣,先列流程:

  • AOP 邏輯介入 BeanFactory 實例化 bean 的過程
  • 根據 Pointcut 定義的匹配規則,判斷當前正在實例化的 bean 是否符合規則
  • 如果符合,代理生成器將切面邏輯 Advice 織入 bean 相關方法中,并為目標 bean 生成代理對象
  • 將生成的 bean 的代理對象返回給 BeanFactory 容器,到此,AOP 邏輯執行結束
  • 對于上面的4步流程,熟悉 Spring AOP 的朋友應該能很容易理解。如果有朋友不理解也沒關系,在后續章節,我會詳細介紹相關流程的具體實現。

    ?3.2 基于 JDK 動態代理的 AOP 實現

    本節說說基于 JDK 動態代理的代理對象生成器具體實現。在 toy-spring 項目中,代理對象生成器的邏輯主要寫在了 JdkDynamicAopProxy 類中,這個類的有兩個方法,其中 getProxy 方法用于生成代理對象。invoke 方法是 InvocationHandler 接口的具體實現,包含了將通知(Advice)織入相關方法中,是3.1節所列流程中第3步流程的具體實現。好了,接下來,對著源碼講解 JdkDynamicAopProxy:

    JdkDynamicAopProxy 實現代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public abstract class AbstractAopProxy implements AopProxy {protected AdvisedSupport advised;public AbstractAopProxy(AdvisedSupport advised) {this.advised = advised;} }/*** 基于 JDK 動態代理的代理對象生成器* Created by code4wt on 17/8/16.*/ final public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler {public JdkDynamicAopProxy(AdvisedSupport advised) {super(advised);}/*** 為目標 bean 生成代理對象* @return bean 的代理對象*/@Overridepublic Object getProxy() {return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getInterfaces(), this);}/*** InvocationHandler 接口中的 invoke 方法具體實現,封裝了具體的代理邏輯* @param proxy* @param method* @param args* @return 代理方法或原方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodMatcher methodMatcher = advised.getMethodMatcher();// 1. 使用方法匹配器 methodMatcher 測試 bean 中原始方法 method 是否符合匹配規則if (methodMatcher != null && methodMatcher.matchers(method, advised.getTargetSource().getTargetClass())) {// 獲取 Advice。MethodInterceptor 的父接口繼承了 AdviceMethodInterceptor methodInterceptor = advised.getMethodInterceptor();/* * 2. 將 bean 的原始方法 method 封裝在 MethodInvocation 接口實現類對象中,* 并把生成的對象作為參數傳給 Adivce 實現類對象,執行通知邏輯*/ return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));} else {// 2. 當前 method 不符合匹配規則,直接調用 bean 的原始方法 methodreturn method.invoke(advised.getTargetSource().getTarget(), args);}} }

    上面貼的代碼,已經對 JdkDynamicAopProxy 實現代碼進行了逐行介解釋,這里不再多說。下面用個流程圖對通知織入邏輯進行總結:


    圖5 toy-spring AOP 通知織入流程圖

    最后對 JdkDynamicAopProxy 進行簡單的測試,測試代碼及結果如下

    測試類:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class LogInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println(invocation.getMethod().getName() + " method start");Object obj= invocation.proceed();System.out.println(invocation.getMethod().getName() + " method end");return obj;} }public class JdkDynamicAopProxyTest {@Testpublic void getProxy() throws Exception {System.out.println("---------- no proxy ----------");HelloService helloService = new HelloServiceImpl();helloService.sayHelloWorld();System.out.println("\n----------- proxy -----------");AdvisedSupport advisedSupport = new AdvisedSupport();advisedSupport.setMethodInterceptor(new LogInterceptor());TargetSource targetSource = new TargetSource(helloService, HelloServiceImpl.class, HelloServiceImpl.class.getInterfaces());advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodMatcher((Method method, Class beanClass) -> true);helloService = (HelloService) new JdkDynamicAopProxy(advisedSupport).getProxy();helloService.sayHelloWorld();} }

    測試結果:

    為了控制文章篇幅,上面代碼中用到的其他輔助類,這里就不貼出來了,想看的朋友可以到 github 上下載源碼。

    ?3.3 AOP 與 IOC 協作

    上一節介紹了3.1節所列流程中第3步流程的具體實現,這一節則會介紹1、2、4步流程的具體實現。在介紹之前,還要再次提一下 BeanPostProcessor接口。在之前2.4節 注冊 BeanPostProcessor 中我已經介紹過 BeanPostProcessor 的作用,也說到了 AOP 是通過 BeanPostProcessor 接口與 IOC 產生聯系的。不過2.4節,只是蜻蜓點水提了一下,沒有詳細展開說明。在本節中,我將詳細講解 toy-spring 項目中 AOP 和 IOC 是怎樣被整合到一起的。

    Spring 從2.0版本開始集成 AspectJ,通過集成 AspectJ,也使得 Spring AOP 的功能得到了很大的增強。我們在平時開發中,很多時候是使用基于 AspectJ 表達式及其他配置來實現切面功能。所以我在編寫 toy-spring 項目時,也在項目中簡單集成了 AspectJ。通過集成 AspectJ,使得 toy-spring AOP 可以基于 AspectJ 表達式完成復雜的匹配邏輯。接下來就讓我們看看袖珍版 Spring AOP 是怎樣實現的吧。

    在 toy-spring 中,AOP 和 IOC 產生聯系的具體實現類是 AspectJAwareAdvisorAutoProxyCreator(下面簡稱 AutoProxyCreator),這個類實現了 BeanPostProcessor 和 BeanFactoryAware 接口。BeanFactory 在注冊 BeanPostProcessor 接口相關實現類的階段,會將其本身注入到 AutoProxyCreator 中,為后面 AOP 給 bean 生成代理對象做準備。BeanFactory 初始化結束后,AOP 與 IOC 橋梁類 AutoProxyCreator 也完成了實例化,并被緩存在 BeanFactory 中,靜待 BeanFactory 實例化 bean。當外部產生調用,BeanFactory 開始實例化 bean 時。AutoProxyCreator 就開始悄悄的工作了,工作細節如下:

  • 從 BeanFactory 查找實現了 PointcutAdvisor 接口的切面對象,切面對象中包含了實現 Pointcut 和 Advice 接口的對象。
  • 使用 Pointcut 中的表達式對象匹配當前 bean 對象。如果匹配成功,進行下一步。否則終止邏輯,返回 bean。
  • JdkDynamicAopProxy 對象為匹配到的 bean 生成代理對象,并將代理對象返回給 BeanFactory。
  • 經過上面3步,AutoProxyCreator 就悄無聲息的把原來的 bean 替換為代理對象了,是不是有種偷天換日的感覺。最后把 toy-spring AOP 剩余的實現代碼貼出來:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class AspectJAwareAdvisorAutoProxyCreator implements BeanPostProcessor, BeanFactoryAware {private XmlBeanFactory xmlBeanFactory;@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {/* 這里兩個 if 判斷很有必要,如果刪除將會使程序進入死循環狀態,* 最終導致 StackOverflowError 錯誤發生*/if (bean instanceof AspectJExpressionPointcutAdvisor) {return bean;}if (bean instanceof MethodInterceptor) {return bean;}// 1. 從 BeanFactory 查找 AspectJExpressionPointcutAdvisor 類型的對象List<AspectJExpressionPointcutAdvisor> advisors =xmlBeanFactory.getBeansForType(AspectJExpressionPointcutAdvisor.class);for (AspectJExpressionPointcutAdvisor advisor : advisors) {// 2. 使用 Pointcut 對象匹配當前 bean 對象if (advisor.getPointcut().getClassFilter().matchers(bean.getClass())) {ProxyFactory advisedSupport = new ProxyFactory();advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());TargetSource targetSource = new TargetSource(bean, bean.getClass(), bean.getClass().getInterfaces());advisedSupport.setTargetSource(targetSource);// 3. 生成代理對象,并返回return advisedSupport.getProxy();}}// 2. 匹配失敗,返回 beanreturn bean;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws Exception {xmlBeanFactory = (XmlBeanFactory) beanFactory;} }

    ProxyFactory 實現代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 13 /*** AopProxy 實現類的工廠類*/ public class ProxyFactory extends AdvisedSupport implements AopProxy {@Overridepublic Object getProxy() {return createAopProxy().getProxy();}private AopProxy createAopProxy() {return new JdkDynamicAopProxy(this);} }

    測試類:

    1 2 3 4 5 6 7 8 9 10 public class XmlBeanFactoryTest {@Testpublic void getBean() throws Exception {System.out.println("--------- AOP test ----------");String location = getClass().getClassLoader().getResource("spring.xml").getFile();XmlBeanFactory bf = new XmlBeanFactory(location);HelloService helloService = (HelloService) bf.getBean("helloService");helloService.sayHelloWorld();} }

    測試結果:

    ?4. 寫在最后

    到此,本文的主要內容寫完了。如果你耐心的讀完了文章,并感覺不錯的話,歡迎猛點贊和收藏按鈕。這篇文章花了我一天的時間,寫的實在有點累,也深感認真寫博客的不易。本篇文章與?仿照 Spring 實現簡單的 IOC 和 AOP - 上篇,Spring bean的生命流程?共三篇文章,對 Spring IOC 和 AOP 的實現原理進行了較為詳細的結束。也是通過認真編寫這三篇文章,使得我對 Spring 框架原理有了更進一步的認識。當然限于我的經驗和能力,以上三篇文章中可能存在著一些錯誤。如果這些錯誤給大家造成了干擾,我表示很抱歉。所以文章若有疏漏不妥之處,還請指出來,如果能不吝賜教,那就更好了。好了,最后感謝大家耐心讀完我的文章,下次再見。

    ?參考:

    • 《Spring揭秘》

    • tiny-spring

    ?附錄:Spring 源碼分析文章列表

    ?Ⅰ. IOC

    更新時間標題
    2018-05-30Spring IOC 容器源碼分析系列文章導讀
    2018-06-01Spring IOC 容器源碼分析 - 獲取單例 bean
    2018-06-04Spring IOC 容器源碼分析 - 創建單例 bean 的過程
    2018-06-06Spring IOC 容器源碼分析 - 創建原始 bean 對象
    2018-06-08Spring IOC 容器源碼分析 - 循環依賴的解決辦法
    2018-06-11Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象
    2018-06-11Spring IOC 容器源碼分析 - 余下的初始化工作

    ?Ⅱ. AOP

    更新時間標題
    2018-06-17Spring AOP 源碼分析系列文章導讀
    2018-06-20Spring AOP 源碼分析 - 篩選合適的通知器
    2018-06-20Spring AOP 源碼分析 - 創建代理對象
    2018-06-22Spring AOP 源碼分析 - 攔截器鏈的執行過程

    ?Ⅲ. MVC

    更新時間標題
    2018-06-29Spring MVC 原理探秘 - 一個請求的旅行過程
    2018-06-30Spring MVC 原理探秘 - 容器的創建過程
    • 本文鏈接:?https://www.tianxiaobo.com/2018/01/18/自己動手實現的-Spring-IOC-和-AOP-下篇/

    from:http://www.tianxiaobo.com/2018/01/18/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E5%AE%9E%E7%8E%B0%E7%9A%84-Spring-IOC-%E5%92%8C-AOP-%E4%B8%8B%E7%AF%87/?

    總結

    以上是生活随笔為你收集整理的自己动手实现的 Spring IOC 和 AOP - 下篇的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。