衆所周知,spring最核心的兩個功能是aop和ioc,即面向切面,控制反轉。這裏咱們探討一下如何使用spring aop。 html
aop全稱Aspect Oriented Programming,面向切面,AOP主要實現的目的是針對業務處理過程當中的切面進行提取,它所面對的是處理過程當中的某個步驟或階段,以得到邏輯過程當中各部分之間低耦合性的隔離效果。其與設計模式完成的任務差很少,是提供另外一種角度來思考程序的結構,來彌補面向對象編程的不足。 spring
通俗點講就是提供一個爲一個業務實現提供切面注入的機制,經過這種方式,在業務運行中將定義好的切面經過切入點綁定到業務中,以實現將一些特殊的邏輯綁定到此業務中。 編程
好比,如果須要一個記錄日誌的功能,首先想到的是在方法中經過log4j或其餘框架來進行記錄日誌,但寫下來發現一個問題,在整個業務中其實核心的業務代碼並無多少,都是一些記錄日誌或其餘輔助性的一些代碼。並且不少業務有須要相同的功能,好比都須要記錄日誌,這時候又須要將這些記錄日誌的功能複製一遍,即便是封裝成框架,也是須要調用之類的。在此處使用複雜的設計模式又得不償失。 設計模式
因此就須要面向切面出場了。 數組
原本spring就自帶一套aop實現,咱們直接使用此實現便可,原本使用aop還須要定義一些xml文件,但因爲咱們使用的是spring-boot框架,這一步就省略掉了。也就是說,在spring-boot中,咱們能夠直接使用aop而不須要任何的配置 緩存
具體如何搭建spring-boot請參考:http://www.cnblogs.com/lic309/p/4073307.html app
先介紹一些aop的名詞,其實這些名詞對使用aop沒什麼影響,但爲了更好的理解最好知道一些 框架
切面(Aspect):一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關於橫切關注點的很好的例子。在Spring AOP中,切面可使用基於模式或者基於@Aspect註解的方式來實現。 ide
鏈接點(Joinpoint):在程序執行過程當中某個特定的點,好比某方法調用的時候或者處理異常的時候。在Spring AOP中,一個鏈接點老是表示一個方法的執行。 模塊化
通知(Advice):在切面的某個特定的鏈接點上執行的動做。其中包括了「around」、「before」和「after」等不一樣類型的通知(通知的類型將在後面部分進行討論)。許多AOP框架(包括Spring)都是以攔截器作通知模型,並維護一個以鏈接點爲中心的攔截器鏈。
切入點(Pointcut):匹配鏈接點的斷言。通知和一個切入點表達式關聯,並在知足這個切入點的鏈接點上運行(例如,當執行某個特定名稱的方法時)。切入點表達式如何和鏈接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法。
引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱爲鏈接類型聲明(inter-type declaration))。Spring容許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可使用引入來使一個bean實現IsModified接口,以便簡化緩存機制。
目標對象(Target Object):被一個或者多個切面所通知的對象。也被稱作被通知(advised)對象。既然Spring AOP是經過運行時代理實現的,這個對象永遠是一個被代理(proxied)對象。
AOP代理(AOP Proxy):AOP框架建立的對象,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。
織入(Weaving):把切面鏈接到其它的應用程序類型或者對象上,並建立一個被通知的對象。這些能夠在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。
其中重要的名詞有:切面,切入點
可能直接說會很模糊,這裏我先作了一個小例子:直接上代碼
//描述切面類 @Aspect @Configuration public class TestAop { /* * 定義一個切入點 */ // @Pointcut("execution (* findById*(..))") @Pointcut("execution(* com.test.service.CacheDemoService.find*(..))") public void excudeService(){} /* * 經過鏈接點切入 */ @Before("execution(* findById*(..)) &&" + "args(id,..)") public void twiceAsOld1(Long id){ System.err.println ("切面before執行了。。。。id==" + id); } @Around("excudeService()") public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint){ System.err.println ("切面執行了。。。。"); try { Thing thing = (Thing) thisJoinPoint.proceed (); thing.setName (thing.getName () + "========="); return thing; } catch (Throwable e) { e.printStackTrace (); } return null; } }
看上面的例子就是實現了一個切面,其中有一些特殊的東西,下面一一解釋:
@Aspect :描述一個切面類,定義切面類的時候須要打上這個註解
@Configuration:spring-boot配置類
1. @Pointcut:聲明一個切入點,切入點決定了鏈接點關注的內容,使得咱們能夠控制通知何時執行。Spring AOP只支持Spring bean的方法執行鏈接點。因此你能夠把切入點看作是Spring bean上方法執行的匹配。一個切入點聲明有兩個部分:一個包含名字和任意參數的簽名,還有一個切入點表達式,該表達式決定了咱們關注那個方法的執行。
注:做爲切入點簽名的方法必須返回void 類型
Spring AOP支持在切入點表達式中使用以下的切入點指示符:
execution - 匹配方法執行的鏈接點,這是你將會用到的Spring的最主要的切入點指示符。
within - 限定匹配特定類型的鏈接點(在使用Spring AOP的時候,在匹配的類型中定義的方法的執行)。
this - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中bean reference(Spring AOP 代理)是指定類型的實例。
target - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中目標對象(被代理的應用對象)是指定類型的實例。
args - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中參數是指定類型的實例。
@target - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中正執行對象的類持有指定類型的註解。
@args - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中實際傳入參數的運行時類型持有指定類型的註解。
@within - 限定匹配特定的鏈接點,其中鏈接點所在類型已指定註解(在使用Spring AOP的時候,所執行的方法所在類型已指定註解)。
@annotation - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中鏈接點的主題持有指定的註解。
其中execution使用最頻繁,即某方法執行時進行切入。定義切入點中有一個重要的知識,即切入點表達式,咱們一會在解釋怎麼寫切入點表達式。
切入點意思就是在何時切入什麼方法,定義一個切入點就至關於定義了一個「變量」,具體什麼時間使用這個變量就須要一個通知。
即將切面與目標對象鏈接起來。
如例子中所示,通知都可以經過註解進行定義,註解中的參數爲切入點。
spring aop支持的通知:
@Before:前置通知:在某鏈接點以前執行的通知,但這個通知不能阻止鏈接點以前的執行流程(除非它拋出一個異常)。
@AfterReturning :後置通知:在某鏈接點正常完成後執行的通知,一般在一個匹配的方法返回的時候執行。
能夠在後置通知中綁定返回值,如:
@AfterReturning( pointcut=com.test.service.CacheDemoService.findById(..))", returning="retVal") public void doFindByIdCheck(Object retVal) { // ... }
@AfterThrowing:異常通知:在方法拋出異常退出時執行的通知。
@After 最終通知。當某鏈接點退出的時候執行的通知(不管是正常返回仍是異常退出)。
@Around:環繞通知:包圍一個鏈接點的通知,如方法調用。這是最強大的一種通知類型。環繞通知能夠在方法調用先後完成自定義的行爲。它也會選擇是否繼續執行鏈接點或直接返回它本身的返回值或拋出異常來結束執行。
環繞通知最麻煩,也最強大,其是一個對方法的環繞,具體方法會經過代理傳遞到切面中去,切面中可選擇執行方法與否,執行方法幾回等。
環繞通知使用一個代理ProceedingJoinPoint類型的對象來管理目標對象,因此此通知的第一個參數必須是ProceedingJoinPoint類型,在通知體內,調用ProceedingJoinPoint的proceed()方法會致使後臺的鏈接點方法執行。proceed 方法也可能會被調用而且傳入一個Object[]對象-該數組中的值將被做爲方法執行時的參數。
任何通知方法能夠將第一個參數定義爲org.aspectj.lang.JoinPoint類型(環繞通知須要定義第一個參數爲ProceedingJoinPoint類型,它是 JoinPoint 的一個子類)。JoinPoint接口提供了一系列有用的方法,好比 getArgs()(返回方法參數)、getThis()(返回代理對象)、getTarget()(返回目標)、getSignature()(返回正在被通知的方法相關信息)和 toString()(打印出正在被通知的方法的有用信息)
有時候咱們定義切面的時候,切面中須要使用到目標對象的某個參數,如何使切面能獲得目標對象的參數。
從例子中能夠看出一個方法:
使用args來綁定。若是在一個args表達式中應該使用類型名字的地方 使用一個參數名字,那麼當通知執行的時候對應的參數值將會被傳遞進來。
@Before("execution(* findById*(..)) &&" + "args(id,..)") public void twiceAsOld1(Long id){ System.err.println ("切面before執行了。。。。id==" + id); }
@Around("execution(List<Account> find*(..)) &&" + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
如今咱們介紹一下最重要的切入點表達式:
如上文所說,定義切入點時須要一個包含名字和任意參數的簽名,還有一個切入點表達式,就是* findById*(..)這一部分。
切入點表達式的格式:execution([可見性] 返回類型 [聲明類型].方法名(參數) [異常])
其中【】中的爲可選,其餘的還支持通配符的使用:
*:匹配全部字符
..:通常用於匹配多個包,多個參數
+:表示類及其子類
運算符有:&&、||、!
切入點表達式關鍵詞:
1)execution:用於匹配子表達式。
//匹配com.cjm.model包及其子包中全部類中的全部方法,返回類型任意,方法參數任意
@Pointcut("execution(* com.cjm.model..*.*(..))")
public void before(){}
2)within:用於匹配鏈接點所在的Java類或者包。
//匹配Person類中的全部方法
@Pointcut("within(com.cjm.model.Person)")
public void before(){}
//匹配com.cjm包及其子包中全部類中的全部方法
@Pointcut("within(com.cjm..*)")
public void before(){}
3) this:用於向通知方法中傳入代理對象的引用。
@Before("before() && this(proxy)")
public void beforeAdvide(JoinPoint point, Object proxy){
//處理邏輯
}
4)target:用於向通知方法中傳入目標對象的引用。
@Before("before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//處理邏輯
}
5)args:用於將參數傳入到通知方法中。
@Before("before() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
//處理邏輯
}
6)@within :用於匹配在類一級使用了參數肯定的註解的類,其全部方法都將被匹配。@Pointcut("@within(com.cjm.annotation.AdviceAnnotation)") - 全部被@AdviceAnnotation標註的類都將匹配
public void before(){}
7)@target :和@within的功能相似,但必需要指定註解接口的保留策略爲RUNTIME。
@Pointcut("@target(com.cjm.annotation.AdviceAnnotation)")
public void before(){}
8)@args :傳入鏈接點的對象對應的Java類必須被@args指定的Annotation註解標註。
@Before("@args(com.cjm.annotation.AdviceAnnotation)")
public void beforeAdvide(JoinPoint point){
//處理邏輯
}
9)@annotation :匹配鏈接點被它參數指定的Annotation註解的方法。也就是說,全部被指定註解標註的方法都將匹配。
@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)")
public void before(){}10)bean:經過受管Bean的名字來限定鏈接點所在的Bean。該關鍵詞是Spring2.5新增的。 @Pointcut("bean(person)") public void before(){}