javascript
Spring 学习笔记(二)Spring AOP
前言
容器和AOP是Spring的兩大核心。本文將來學習Spring AOP。
AOP是什么?
AOP在計算機科學領域還是相對年輕的概念,由Xerox PARC公司發明。Gregor Kiczales 在1997年領導一隊研究人員首次介紹了AOP。當時他們關心的問題是如何在大型面向對象的代碼庫中重復使用那些必要且代價高的樣板,那些樣板的通用例子具有日志,緩存和事務功能。在最終的研究報告中,Kiczales和他的團隊描述了OOP技術不能捕獲和解決的問題,他們發現橫切關注點最終分散在整個代碼中,這種交錯的代碼會變得越來越難開發和維護。他們分析了所有技術原因,包括為何這種糾纏模式會出現,為什么避免起來這么困難,甚至涉及了設計模式的正確使用。該報告描述了一種解決方案作為OOP的補充,即使用“切面aspects”封裝橫切關注點以及允許重復使用。最終實現了AspectJ,就是今天Java開發者仍然使用的一流AOP工具。也就是說,AOP可不是Spring發明的,Spring只是對AOP做了支持而已。既然如此,AOP里面的幾個概念就是通用的了。
《Spring in Action》這本書給出了明確的解釋:
在軟件開發中,散布于應用中多處的功能被稱為橫切關注點(cross-cutting concern)。通常來講,這些橫切關注點從概念上是與應用的業務邏輯相分離的。比如:日志、聲明式事物、安全和緩存。這些東西都不是我們平時寫代碼的核心功能,但許多地方都要用到。
把這些橫切關注點與業務相分離正是面向切面編程(AOP)索要解決的問題。
簡單的說就是把這些許多地方都要用到,但又不是核心業務的功能,單獨剝離出來封裝,通過配置指定要切入到指定的方法中去。
如上圖所示,這就是橫切關注點的概念,水平的是核心業務,這些切入的箭頭就是我們的橫切關注點。
橫切關注點可以被模塊化為特殊的類,這些類被稱為切面(aspect)。這樣做有兩個好處:
首先,現在每個關注點都集中于一個地方,而不是分割到多處代碼中
其次,服務模塊更簡潔,因為它們只包含主要關注點(或核心功能)的代碼,而次要關注點的代碼被轉移到切面中了。
AOP術語
通知(Advice):
在AOP中,切面的工作被稱為通知。通知定義了切面“是什么”以及“何時”使用。除了描述切面要完成的工作,通知還解決了何時執行這個工作的問題。
Spring切面可以應用5種類型的通知:
- 前置通知(Before):在目標方法被調用之前調用通知功能
- 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么
- 返回通知(After-returning):在目標方法成功執行之后調用通知
- 異常通知(After-throwing):在目標方法拋出異常后調用通知
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為
連接點(Join point):
連接點是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,并添加行為。
切點(Pointcut):
如果說通知定義了切面“是什么”和“何時”的話,那么切點就定義了“何處”。比如我想把日志引入到某個具體的方法中,這個方法就是所謂的切點。
切面(Aspect):
切面是通知和切點的結合。通知和切點共同定義了切面的全部內容———他是什么,在何時和何處完成其功能。
引入(Introduction):
引入允許我們向現有的類添加新的方法和屬性(Spring提供了一個方法注入的功能)。
織入(Weaving):
把切面應用到目標對象來創建新的代理對象的過程,織入一般發生在如下幾個時機:
- 編譯時:當一個類文件被編譯時進行織入,這需要特殊的編譯器才可以做的到,例如AspectJ的織入編譯器。
- 類加載時:使用特殊的 ClassLoader 在目標類被加載到程序之前增強類的字節代碼。
- 運行時:切面在運行的某個時刻被織入, 方式是容器為目標對象動態地創建一個代理對象。Spring AOP就是以這種方式織入切面的。
Spring對AOP的支持
同依賴注入一樣,Spring AOP也提供兩種配置:
- 基于Java Annotation;
- 基于Xml配置;
Spring AOP是基于動態代理實現的,代理類封裝了目標類,并攔截被通知方法的調用,再把調用轉發給真正的目標bean。至于什么是動態代理,后文將會解釋。
動態代理只能處理方法,因此Spring AOP只支持方法連接點。但是通常方法攔截可以滿足大部分需求。如果需要字段或構造器攔截,別忘了Spring是一個高度可擴展的框架,可以使用AspectJ 等第三方AOP框架。
下面就舉兩個例子分別來介紹下Spring AOP的兩種配置方式,我們就拿簡單的日志來說明。
使用Java注解配置
@Component //聲明bean @Aspect //該注解標示該類為切面類 public class LogAspect {@Pointcut("execution(* com.springdemo.service.impl.UserServiceImpl.*(..))")public void logAop(){}@Before("logAop() && args(name)")public void logBefore(String name){System.out.println(name+"前置通知Before");}@AfterReturning("logAop()")public void logAfterReturning(){System.out.println("返回通知AfterReturning");}@After("logAop() && args(name)")public void logAfter(String name){System.out.println(name+"后置通知After");}@AfterThrowing("logAop()")public void logAfterThrow(){System.out.println("異常通知AfterThrowing");} }該代碼片段就聲明了一個bean,而這個bean是一個切面,其中的方法將會應用于com.springdemo.service.impl.UserServiceImpl類的任何方法。
UserServiceImpl類很簡單,只是個普通的bean:
package com.springdemo.service.impl; @Service("userService") public class UserServiceImpl implements UserService{@Overridepublic void sayHello(String name) {System.out.println("hello, "+name);} }最后,還要啟用Spring AOP的注解配置。同樣采用Java配置:
@Configuration @ComponentScan @EnableAspectJAutoProxy public class Config{ ... } 注意不要被注解里的“AspectJ”字樣嚇到了,這個注解確實是AspectJ定義的,但Spring只是重用了這個注解,實現還是Spring AOP自己的基于動態代理的實現。sayHello被執行后的結果:
aop前置通知Before hello, aop aop后置通知After 返回通知AfterReturning本段代碼源自嘟嘟的博客,里面有具體的解釋。
基于XML的配置
同理,不再列出詳細代碼。
- <aop:aspectj-autoproxy/> 啟用AOP注解。
- 如不采用注解,則用aop名稱空間的標記描述切面。如下是一個等效的配置:
Spring AOP原理 —— 動態代理
參考這篇知乎問答。
首先,靜態代理應該是普遍了解的概念了。它是OOP的一種設計模式,依賴于接口。
動態代理則是為了解決目標類方法越來越多時,代理類也要跟著膨脹的問題。因為動態代理類只要在invoke()方法中有選擇地實現接口的方法。這對那些普遍適用的功能來說特別適合,比如緩存、認證、log等等。
至于InvocationHandler,它是JDK的java.lang.reflect名稱空間里提供的一個類,用來實現動態代理。具體可見Java Dynamic Proxy API。
可以看出,動態代理的功能十分強大,因此得到了廣泛的應用。比如單元測試中的mock框架,MyBatis的sql注解等等。
最后,動態代理仍是需要接口的。而借助另一個第三方類庫CGLib,則可以動態生成字節碼,不依賴于接口。Spring AOP對這兩種技術都有使用。參見CSDN。
總結
以上是生活随笔為你收集整理的Spring 学习笔记(二)Spring AOP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2017年总结
- 下一篇: “不正经”NIPS大会指北:嘻哈歌手、感