原文:http://wayfarer.cnblogs.com/articles/241024.html html
3.1.1 概覽java
AOP(Aspect-Oriented Programming,面向切面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP容許你定義從上到下的關係,但並不適合定義從左到右的關係,例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。git
而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即切面。所謂「切面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。AOP表明的是一個橫向的關係,若是說「對象」是一個空心的圓柱體,其中封裝的是對象的屬性和行爲;那麼面向切面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以得到其內部的消息。而後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。github
使用「橫切」技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處都基本類似。好比權限認證、日誌、事務處理。Aop 的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是「將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。」web
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法建立「切面」,從而使得編譯器能夠在編譯期間織入有關「切面」的代碼。然而異曲同工,實現AOP的技術特性倒是相同的,分別爲:spring
一、切面(Aspect):一個橫切關注點的模塊化,這個關注點可能會橫切多個對象,它相似宇OOP中定義的一個類,但更多的是描述對象間橫向的關係。express
二、鏈接點(Joinpoint):在程序執行過程當中某個特定的點,好比某方法調用的時候或者處理異常的時候,它是一個抽象的概念,在實現AOP時,並不須要去定義一個join point。在Spring AOP中,一個鏈接點老是表示一個方法的執行。編程
三、切入點(Pointcut):一個捕獲鏈接點的結構,能夠認爲是鏈接點的集合。設計模式
四、通知(Advice):在切面的某個特定的鏈接點上執行的動做,是執行「切面」的具體邏輯。安全
五、引入(Introduction):用來給一個對象聲明額外的方法或屬性,從而達到修改對象結構的目的
六、目標對象(Target Object): 被一個或者多個切面所「橫切」的對象,是須要被加強的對象。
七、AOP代理(AOP Proxy): AOP框架使用代理模式建立的對象,從而實如今鏈接點處插入加強(即應用切面),就是經過代理來對目標對象應用切面。
八、織入(Weaving):織入是一個過程,是將切面應用到目標對象從而建立出AOP代理對象的過程,織入能夠在編譯期、類裝載期、運行期進行。
上述的技術特性組成了基本的AOP技術,大多數AOP工具均實現了這些技術。它們也能夠是研究AOP技術的基本術語。
3.1.2 橫切技術
「橫切」是AOP的專有名詞。它是一種蘊含強大力量的相對簡單的設計和編程技術,尤爲是用於創建鬆散耦合的、可擴展的企業系統時。橫切技術可使得AOP在一個給定的編程模型中穿越既定的職責部分(好比日誌記錄和性能優化)的操做。
若是不使用橫切技術,軟件開發是怎樣的情形呢?在傳統的程序中,因爲橫切行爲的實現是分散的,開發人員很難對這些行爲進行邏輯上的實現或更改。例如,用於日誌記錄的代碼和主要用於其它職責的代碼纏繞在一塊兒。根據所解決的問題的複雜程度和做用域的不一樣,所引發的混亂可大可小。更改一個應用程序的日誌記錄策略可能涉及數百次編輯——即便可行,這也是個使人頭疼的任務。
在AOP中,咱們將這些具備公共邏輯的,與其餘模塊的核心邏輯糾纏在一塊兒的行爲稱爲「橫切關注點(Crosscutting Concern)」,由於它跨越了給定編程模型中的典型職責界限。
3.1.2.1 橫切關注點
一個關注點(concern)就是一個特定的目的,一塊咱們感興趣的區域,一段咱們須要的邏輯行爲。從技術的角度來講,一個典型的軟件系統包含一些核心的關注點和系統級的關注點。舉個例子來講,一個信用卡處理系統的核心關注點是借貸/存入處理,而系統級的關注點則是日誌、事務完整性、受權、安全及性能問題等,許多系統級關注點——即橫切關注點(crosscutting concerns)——會在多個模塊中出現。若是使用現有的編程方法,橫切關注點會橫越多個模塊,結果是使系統難以設計、理解、實現和演進。AOP可以比上述方法更好地分離系統關注點,從而提供模塊化的橫切關注點。
例如一個複雜的系統,它由許多關注點組合實現,如業務邏輯、性能,數據存儲、日誌和調度信息、受權、安全、線程、錯誤檢查等,還有開發過程當中的關注點,如易懂、易維護、易追查、易擴展等,圖2.1演示了由不一樣模塊實現的一批關注點組成一個系統。
圖3.1 把模塊做爲一批關注點來實現
經過對系統需求和實現的識別,咱們能夠將模塊中的這些關注點分爲:核心關注點和橫切關注點。對於核心關注點而言,一般來講,實現這些關注點的模塊是相互獨立的,他們分別完成了系統須要的商業邏輯,這些邏輯與具體的業務需求有關。而對於日誌、安全、持久化等關注點而言,他們倒是商業邏輯模塊所共同須要的,這些邏輯分佈於核心關注點的各處。在AOP中,諸如這些模塊,都稱爲橫切關注點。應用AOP的橫切技術,關鍵就是要實現對關注點的識別。
若是將整個模塊比喻爲一個圓柱體,那麼關注點識別過程能夠用三棱鏡法則來形容,穿越三棱鏡的光束(指需求),照射到圓柱體各處,得到不一樣顏色的光束,最後識別出不一樣的關注點。如圖3.2所示:
圖3.2 關注點識別:三棱鏡法則
上圖識別出來的關注點中,Business Logic屬於核心關注點,它會調用到Security,Logging,Persistence等橫切關注點。
public class BusinessLogic
{
public void SomeOperation()
{
//驗證安全性;Securtity關注點;
//執行前記錄日誌;Logging關注點;
DoSomething();
//保存邏輯運算後的數據;Persistence關注點;
//執行結束記錄日誌;Logging關注點;
}
}
AOP的目的,就是要將諸如Logging之類的橫切關注點從BusinessLogic類中分離出來。利用AOP技術,能夠對相關的橫切關注點封裝,造成單獨的「aspect」。這就保證了橫切關注點的複用。因爲BusinessLogic類中再也不包含橫切關注點的邏輯代碼,爲達到調用橫切關注點的目的,能夠利用橫切技術,截取BusinessLogic類中相關方法的消息,例如SomeOperation()方法,而後將這些「aspect」織入到該方法中。例如圖3.3:
圖3.3 將橫切關注點織入到核心關注點中
經過利用AOP技術,改變了整個系統的設計方式。在分析系統需求之初,利用AOP的思想,分離出核心關注點和橫切關注點。在實現了諸如日誌、事務管理、權限控制等橫切關注點的通用邏輯後,開發人員就能夠專一於核心關注點,將精力投入到解決企業的商業邏輯上來。同時,這些封裝好了的橫切關注點提供的功能,能夠最大限度地複用於商業邏輯的各個部分,既不須要開發人員做特殊的編碼,也不會由於修改橫切關注點的功能而影響具體的業務功能。
AOP技術的優點是顯而易見的。在面向對象的世界裏,人們提出了各類方法和設計原則來保障系統的可複用性與可擴展性,以期創建一個鬆散耦合、便於擴展的軟件系統。例如GOF提出的「設計模式」,爲咱們提供了設計的典範與準則。設計模式經過最大程度的利用面向對象的特性,諸如利用繼承、多態,對責任進行分離、對依賴進行倒置,面向抽象,面向接口,最終設計出靈活、可擴展、可重用的類庫、組件,乃至於整個系統的架構。在設計的過程當中,經過各類模式體現對象的行爲、暴露的接口、對象間關係、以及對象分別在不一樣層次中表現出來的形態。然而鑑於對象封裝的特殊性,「設計模式」的觸角始終在接口與抽象中大作文章,而對於對象內部則無能爲力。
經過「橫切」技術,AOP技術就能深刻到對象內部翻雲覆雨,截取方法之間傳遞的消息爲我所用。因爲將核心關注點與橫切關注點徹底隔離,使得咱們可以獨立的對「切面」編程。它容許開發者動態地修改靜態的OO模型,構造出一個可以不斷增加以知足新增需求的系統,就象現實世界中的對象會在其生命週期中不斷改變自身,應用程序也能夠在發展中擁有新的功能。
設計軟件系統時應用AOP技術,其優點在於:
(一)在定義應用程序對某種服務(例如日誌)的全部需求的時候。經過識別關注點,使得該服務可以被更好的定義,更好的被編寫代碼,並得到更多的功能。這種方式還可以處理在代碼涉及到多個功能的時候所出現的問題,例如改變某一個功能可能會影響到其它的功能,在AOP中把這樣的麻煩稱之爲「糾結(tangling)」。
(二)利用AOP技術對離散的切面進行的分析將有助於爲開發團隊指定一位精於該項工做的專家。負責這項工做的最佳人選將能夠有效利用本身的相關技能和經驗。
(三)持久性。標準的面向對象的項目開發中,不一樣的開發人員一般會爲某項服務編寫相同的代碼,例如日誌記錄。隨後他們會在本身的實施中分別對日誌進行處理以知足不一樣單個對象的需求。而經過建立一段單獨的代碼片斷,AOP提供瞭解決這一問題的持久簡單的方案,這一方案強調了將來功能的重用性和易維護性:不須要在整個應用程序中一遍遍從新編寫日誌代碼,AOP使得僅僅編寫日誌方面(logging aspect)成爲可能,而且能夠在這之上爲整個應用程序提供新的功能。
總而言之,AOP技術的優點使得須要編寫的代碼量大大縮減,節省了時間,控制了開發成本。同時也使得開發人員能夠集中關注於系統的核心商業邏輯。此外,它更利於建立鬆散耦合、可複用與可擴展的大型軟件系統。
(1)、AOP Hello World
開發環境搭建過程就不說了,直接上代碼。
核心業務代碼
/** * 核心業務接口 * @author FF * */ public interface PersionDao { public void savePersion(); } /** * 核心業務類 * @author FF * */ public class PersionDaoImp implements PersionDao { @Override public void savePersion() { System.out.println("save persion…………"); } }
切面代碼
/** * 事務相關的切面 * @author FF * */ public class Transaction { public void begin() { System.out.println("transaction begin"); } public void commit() { System.out.println("transaction commit"); } }
測試代碼
public class test { private ApplicationContext applicationContext; @Before public void init() { applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void myTest() { PersionDao persionDao = (PersionDao) applicationContext.getBean("persionDao"); persionDao.savePersion(); } }
applicationContext.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"> <bean id="persionDao" class="target.PersionDaoImp"></bean> <bean id="transaction" class="aspect.Transaction"></bean> <aop:config> <aop:pointcut expression="execution (* target.PersionDaoImp.*(..))" id="perform"/> <aop:aspect ref="transaction"> <aop:before method="begin" pointcut-ref="perform"/> <aop:after-returning method="commit" pointcut-ref="perform"/> </aop:aspect> </aop:config> </beans>
測試結果截圖,運行測試代碼中的myTest()方法,獲得如下結果
注:myTest()方法中getBean獲取到的是一個PersionDaoImpImp對象的代理對象
(3)、AOP配置說明
<aop:config>:aop相關配置
<aop:pointcut>: 定義一個切入點切入點。expression屬性是切入點表達式,id是切入點惟一標識。
<aop:aspect>: 定義一個切面切面。ref屬性是切面類對應的<bean>id。
<aop:before>: 定義一個前置通知。在目標方法以前執行配置的代碼。method屬性是要執行的方法名,pointcut-ref是切入點映射。此外aop:after-returning是後置通知,aop:after是最終通知,aop:after-throwing是異常通知,aop:around是環繞通知。
(4)、切入點表達式說明
切入點表達式與完整方法對應關係圖
其中「?」號結尾的無關緊要,即public……,java.lang.Object……,java.lang.InterruptedException……可省略
Hello World示例中
expression="execution (* target.PersionDaoImp.*(..))"
表示target包下PersionDaoImp類中的全部方法,方法的參數任意
下面是幾個表達式例子:
任意公共方法的執行: execution(public * *(..))
任何一個名字以「set」開始的方法的執行: execution(* set*(..))
AccountService接口定義的任意方法的執行: execution(* com.xyz.service.AccountService.*(..))
在service包中定義的任意方法的執行: execution(* com.xyz.service.*.*(..))
在service包或其子包中定義的任意方法的執行: execution(* com.xyz.service..*.*(..))
在service包中的任意鏈接點(在Spring AOP中只是方法執行): within(com.xyz.service.*)
在service包或其子包中的任意鏈接點(在Spring AOP中只是方法執行): within(com.xyz.service..*)
實現了AccountService接口的代理對象的任意鏈接點 (在Spring AOP中只是方法執行): this(com.xyz.service.AccountService)
實現AccountService接口的目標對象的任意鏈接點 (在Spring AOP中只是方法執行): target(com.xyz.service.AccountService)
任何一個只接受一個參數,而且運行時所傳入的參數是Serializable 接口的鏈接點(在Spring AOP中只是方法執行): args(java.io.Serializable)
(5)、通知種類
前置通知:在目標方法執行以前執行。xml配置文件中用<aop:before>配置
後置通知:在目標方法執行以後執行,能夠獲取目標方法的返回值,當方法遇到異常不執行。xml配置文件中用<aop:after-returning>配置
最終通知:在目標方法執行以後執行,不管目標方法是否遇到異常都執行。相似try catch塊中的finally。
異常通知:獲取目標方法拋出的異常,當目標方法拋出異常時執行。
環繞通知:能控制目標方法的執行。
(6)、Hello World程序執行流程
在Spring容器啓動後:一、首先容器實例化xml文件中配置的bena,persionDao與transaction。二、接着Spring會解析aop配置,解析配置的切入點perform以及切面transaction和切面中配置的各類通知。Spring容器會依據切入點表達式匹配的類在Spring容器中查找,若是找到這個類,會爲其建立代理對象,若是沒有找到會報錯。三、接着Spring爲代理對象生成方法,經過通知+目標方法的方式。四、客戶端經過getBean()方法獲取對象,若是這個對象有代理則返回代理對象,若是沒有返回對象自己。
說明:一、客戶端getBean()時,若是對象存在對應的代理,返回代理對象,若是沒有則返回對象自己。二、若是目標類實現了接口,Spring自動使用jdk動態代理技術生成代理對象,若是目標類沒有實現接口,Spring會使用cglib生成代理對象。生成的代理對象由Spring容器控制。
(7)、AOP的兩個應用案例
一、用aop實現統一的異常處理
思路:建立一個包含異常處理方法的切面,使用這個切面作統一的異常處理。
代碼:https://github.com/littleant2/java-web/tree/master/springAop_exception
二、權限控制
思路:建立一個切面,其中包含權限判斷的通知,將其定義爲環繞通知。
代碼:https://github.com/littleant2/java-web/tree/master/springAop_privilege