javascript
spring aop 必须的包 及里面用到的东西_Spring 原理初探——IoC、AOP
前言
眾所周知, 現在的 Spring 框架已經成為構建企業級 Java 應用事實上的標準了,眾多的企業項目都構建在 Spring 項目及其子項目之上,特別是 Java Web 項目。
Spring 的兩個核心概念是 IoC(控制反轉)和 AOP(面向切面編程)。想了解 Spring 的工作原理,毫無疑問,首先要從這兩個概念的 Spring 實現入手。但是 Spring 源碼浩如煙海,里面摻雜了太多的實現細節,入門可謂極其困難。當我正苦于難以入門時,好友介紹了 tiny-spring 這個開源項目,這個項目用了不到千行的代碼,就將 Spring 的 IoC、AOP 的核心流程實現完畢,真是居家旅行、吹逼面試之必備呀!
廢話少說,我們開始吧!
目錄結構
在 github 上 clone 下項目來之后,我們關注 src 文件夾,其余的是一些愛好者提的注釋 PR,恰巧被作者 merge 了,不必理會。目錄結構是這樣的:
1.aop包,顧名思義,實現了 Spring 的 AOP 功能,可以通過 bean 的自動 AOP 切入,文件稍多,暫時先不展開。 2.bean.factory包,通過BeanFactory、AbstractBeanFactory、AutowireCapableBeanFactory三個類,實現了BeanFactory的核心功能,詳情稍后講解。 3.bean.io包定義了資源加載相關的抽象概念,這里的資源包括 xml 配置文件等。 4.bean.xml包中只包含一個類:XmlBeanDefinitionReader,主要負責在 xml 配置文件中讀取 bean 定義。 5.bean包其他類,定義了 BeanDefinition 等核心概念,詳情后講。 6.context包定義了ApplicationContext的核心概念。 7.BeanReference指的是引用類型的 Bean,而不是實體類。
IOC–浮沙筑臺之根基
IOC(控制翻轉)是一種編程范式,可以在一定程度上解決復雜系統對象耦合度太高的問題,并不是 Spring 的專利。IOC 最常見的方式是 DI(依賴注入),可以通過一個容器,將 Bean 維護起來,方便在其他地方直接使用,而不是重新 new。可以說,IOC 是 Spring 最基本的概念,沒有 IOC 就沒有 Spring。
為什么 DI 可以起到解耦的作用?
一個軟件系統包含了大量的對象,每個對象之間都有依賴關系,在普通的軟件編寫過程中,對象的定義分布在各個文件之中,對象之間的依賴,只能通過類的構造器傳參,方法傳參的形式來完成。當工程變大之后,復雜的邏輯會讓對象之間的依賴梳理變得異常困難。
在 Spring IOC 中,一般情況,我們可以在 XML 文件之中,統一的編寫 bean 的定義,bean 與 bean 之間的依賴關系,從而增加邏輯的清晰度。而且,bean 的創建是由 Spring 來完成的,不需要編程人員關心,編程人員只需要將精力放到業務的邏輯上面,減輕了思維的負擔。
在tiny-spring里面,整個beans和context包都是用來實現IOC的。
beans包實現的核心關注點是BeanFactory,BeanFactory也叫作 Bean 容器,顧名思義,是用來盛放、管理 bean 的。
context包實現的核心關注是ApplicationContext,ApplicationContext也是用來獲取 Bean 的,但是它更高層,它的面向用戶是 Spring 的使用者,而 BeanFactory 面向的用戶更多是 Spring 開發者。BeanFactory 定義了 Bean 初始化的流程,ApplicationContext 定義了從 XML 讀取,到 Bean 初始化,再到使用的過程。
Bean 在哪定義?
剛才有說到,Spring 通常通過 xml 文件,來統一的描述 bean,bean 與 bean 的依賴關系。所以說,bean 的定義表述,發生在 xml 配置文件之中。這個 XML 文件就是我們需要讀取的資源文件。
因此,首要任務就是研究與讀取 XML 資源文件相關的類。
bean.io中存放的是讀取資源文件的抽象概念。其中包含了三個類或者接口:
1.Resource接口,這個接口只有一個方法,InputStream getInputStream() throws IOException;。實現這個接口的類就是一個抽象的資源,可以獲取這個資源的輸入流,從而獲取其中的內容。 2.UrlResource類,這個類實現了Resource接口,通過構造器傳入一個 url 地址,代表的是這個 url 所對應的文件。 3.ResourceLoader類,只有一個方法,public Resource getResource(String location)。輸入 url 的文件地址(并不是真正的 URL 格式地址),來獲取 Resource。
通過分析上面三個類、接口,我們知道,這個包完成了一個任務:通過ResourceLoader這個類,獲取某一個地址的Resource,從而獲取這個文件的輸入流。因為使用了 Resource 概念,可以使用網絡文件或者本地文件。
Bean 如何定義?
1.BeanDefinition是 Bean 定義的核心概念,BeanDefinition包含了:bean 的對象、bean 的類類型、bean 的名字,bean 的所有屬性。這個類對 bean 的基本信息做好一個包裝。 2.BeanDefinitionReader接口,只有一個方法:void loadBeanDefinitions(String location) throws Exception;,實現這個接口的類,具有將某一個文件中的所有 bean 定義載入的功能。所以BeanDefinitionReader定義了,在哪載入 bean 定義,至于載入到哪里、如何載入,稍后看具體實現。 3.AbstractBeanDefinitionReader抽象類,上面剛說了實現了BeanDefinitionReader接口的類,具有將某一個文件中描述的 bean 定義載入的功能,AbstractBeanDefinitionReader就實現了這樣一個抽象功能。它的作用就是定義,載入到哪和如何載入的問題。在這個類里面,有兩個屬性:Map registry;和ResourceLoader resourceLoader;。registry是一個注冊表,他保存的就是所有的 Bean 定義,Map 結構,key 是 bean 的名字,value 就是 BeanDefinition。resourceLoader描述了如何載入。 4.XmlBeanDefinitionReader這是beans.xml包里面的唯一一個方法,也是最重要的方法之一。它繼承了AbstractBeanDefinitionReader,實現了所有方法,解決了 bean 定義中:在哪載入、如何載入、載入到哪的三個大問題。這個類面向用戶的方法有兩個,一個是loadBeanDefinitions,毫無疑問,這個是必須的。另一個是getRegistry用來獲取 bean 注冊表,得到所有 bean 的信息,registry 是 bean 們在內存中實際的家。但是這個getRegistry方法并不是面向用戶的,而是面向 ApplicationContext 的。 5.PropertyValue和PropertyValue代表一種抽象概念,在 xml 中,bean 的屬性包括屬性名和屬性對象,PropertyValue就是這么一個實體。 6.BeanReference代表的是 Bean 的屬性不是真實對象,而是另一個 bean 的引用。
Bean 的組裝全過程
上面兩部分是鋪墊,而 BeanFactory 才是重點對象。beans.factory包中有三個類用來定義 BeanFactory 相關的概念。
1.BeanFactory接口,只有一個方法:Object getBean(String name) throws Exception;,實現這個接口的類,就具有了得到一個 bean 的能力。 2.AbstractBeanFactory類,較為復雜。詳情后講。 3.AutowireCapableBeanFactory繼承了AbstractBeanFactory,實現了 applyPropertyValues 方法,通過反射,將 bean 的所有屬性,通過 set 方法注入進去。
AbstractBeanFactory有三大屬性:
_beanDefinitionMap,類似于 registry,但是他是 BeanFactory 里面私有的,代表的是這個 BeanFactory 里面暫時有哪些 bean 定義。
_beanDefinitionNames代表里面,這個 BeanFactory 里面有哪些 bean(名字)。 *beanPostProcessors,代理處理器,AOP 會用到,詳情后講。
AbstractBeanFactory實現了幾大功能:
_getBean,這是主要功能,可以獲取一個 Bean 對象。
_registerBeanDefinition,面向 ApplicationContext,用來將 XML 配置文件里面的 bean 定義注冊到 BeanFactory 里面。
_preInstantiateSingletons,面向 ApplicationContext,用來在開始的時候,將所有的 bean 都初始化完畢,避免懶加載。
_addBeanPostProcessor添加代理處理器。 *getBeansForType,在 BeanFactory 里面,獲取某一個類型的所有 bean。
經過上面的分析,我們可以知道 BeanFactory 完成了 Bean 初始化的整個流程。BeanFactory 的工作流程如下:
好了,到這 BeanFactory 就講完了,下面是更重要的 ApplicationContext。
ApplicationContext - 用戶與 BeanFactory 之間的橋梁
beans.context包有三個類、接口,完成了 ApplicationContext 的基本功能。
總結 - ApplicationContext 初始化流程
ApplicationContext 初始化流程
總結 - ApplicationContext 獲取 bean 流程
AOP–移花接木之魔法
上一節,講完了 Spring IOC 的整個流程,也就是 bean 從定義獲取,到得到 bean 之間的整個流程。本節,我們接觸一下 Spring 另一個重要概念,AOP。AOP 用途十分廣泛,其中 Spring 內部的聲明式事務和攔截器都是利用了 AOP 的強大威力,才得以優雅的實現。
AOP 是什么呢,簡單來說,它可以讓編程人員在不修改對象代碼的情況下,為這個對象添加額外的功能或者限制。
很熟悉吧,這就是代理模式!
Java 中存在兩種代理模式:
一種叫靜態代理,就是通過接口繼承復用的方式來完成的, 代理類與被代理對象實現相同的接口,然后代理類里面會擁有一個被代理對象,代理類與被代理對象相同的方法,活調用被代理對象的方法,不過中間會加以限制,您翻開任何一本設計模式相關的書,翻到代理模式這一節,講的就是它了。
另一種叫做動態代理,動態代理就是允許我們在程序運行過程中,為動態生成的對象,動態的生成代理。顯然,這比靜態代理靈活太多了。
Java 默認提供了動態代理的實現方式,但是有限制,它要求被代理對象必須實現某一個接口。為了突破這一限制,為普通類也可以提供代理,CGLib 這個庫橫空出世。
因為 AOP 涉及的知識較為復雜,所以我先將背景知識介紹一下。
在 aopallicance 里面,定義了幾個核心概念:
- Advice,增強,實現這個接口,說明這個類負責某一種形式的增強。
- Joinpoint,連接點,表示切點與目標方法連接處的信息。
- MethodInterceptor,繼承了 Interceptor 接口,而 Interceptor 繼承了 Advice 接口,是一種 Advice,但是有一個方法 invoke。這個方法需要一個參數 MethodInvocation。
- MethodInvocation,表示的是連接點的信息以及連接點函數的調用。
結合上面的信息,我們發現,其實 MethodInterceptor 的 invoke 方法,調用的就是 MethodInvocation 的 proceed 方法,而這個 proceed 方法呢,應該調用的肯定是 Method.invoke 方法。所以,這是一種變相調用 method.invoke 的方式。為什么這樣做呢,猜一猜的話,肯定是為了代碼的復用,哈哈哈,這是廢話。
在 Spring 中,還定義了幾個核心概念:
- Pointcut,切點,可以定位類以及方法。
- Advisor,可以獲取一個增強。
- PointcutAdvisor,定義了哪些方法,具有什么類型的增強。
- MethodMatcher,表示某一個方法是否符合條件。
- ClassFilter,定義了某個類是否符合條件。
- TargetSource,被代理的對象,包括對象本身,類類型、所有接口。
- AdvisedSupport,代理相關的元數據,包括被代理的對象,增強等。
- ProxyFactory,代理工廠,可以獲得一個代理對象,同時具有 AdvisedSupport 的所有特性。
- Cglib2AopProxy,使用 cglib 實現的動態代理類,繼承了 AbstractAopProxy 抽象類,這個類的主要方法就是 getProxy,通過什么呢,通過 AdvisorSupport。
- ReflectiveMethodInvocation,可以獲取連接點的信息,代理的對象等。
- JdkDynamicAopProxy,和 Cglib2AopProxy 類一個作用,通過 AdvisorSupport 來 getProxy,不過是使用 Java 自帶的動態代理實現的。
其中,ProxyFactory 是獲取一個代理對象的直接工廠,而這個代理對象,可以通過 Cglib2AopProxy 產生,也可以通過 JdkDynamicAopProxy 產生。
Spring AOP 之所以能夠為動態生成的 Bean 提供代理,得益于 PostProcessor 接口。我們會議 IOC 初始化流程中,最后一部,就是得到 BeanFactory 之中所有繼承了 PostProcessor 接口的 bean,調用它們的 postProcessBeforeInitilization、postProcessAfterInitilization 方法,來代理 bean,生成新的 bean。
基于這個突破口,我們只需要在 xml 配置文件中,放入 PostProcessor 對象,Spring 就會自動的用這寫對象,來代理真正的對象。
在這里,我們的對象是 AspectJAwareAdvisorAutoProxyCreator。
在這個對象的方法中,邏輯是這樣的,找到 xml 里面所有切面 bean,然后在這些 bean 里面,找到符合被代理類的切面 bean,找到切面 bean 之后,就可以獲得增強,切點等,于是可有構造一個 AdvisorSupport,知道了 AdvisorSupport,我們就能夠通過 proxyFactory 來獲取代理了。
至于如何這個類切面是用來切入代理類的,這個就要交給 PointCut 來實現了,pointcut 有很多實現方式,這里我們用的是 aspectj。具體這個類我就不細講了。
到目前位置,我自己已經將整個 AOP 的流程搞清楚了,下面通過流程圖的形式展示出來:
作者: WindMt
來源: WindMt
總結
以上是生活随笔為你收集整理的spring aop 必须的包 及里面用到的东西_Spring 原理初探——IoC、AOP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓按钮设置背景颜色不管用_MIUI10
- 下一篇: 测试私有方法 重构_一个全栈工程师重构之