容器和AOP是Spring的兩大核心。本文未來學習Spring AOP。html
AOP在計算機科學領域仍是相對年輕的概念,由Xerox PARC公司發明。Gregor Kiczales 在1997年領導一隊研究人員首次介紹了AOP。當時他們關心的問題是如何在大型面向對象的代碼庫中重複使用那些必要且代價高的樣板,那些樣板的通用例子具備日誌,緩存和事務功能。在最終的研究報告中,Kiczales和他的團隊描述了OOP技術不能捕獲和解決的問題,他們發現 橫切關注點最終分散在整個代碼中,這種交錯的代碼會變得愈來愈難開發和維護。他們分析了全部技術緣由,包括爲什麼這種糾纏模式會出現,爲何避免起來這麼困難,甚至涉及了設計模式的正確使用。該報告描述了一種解決方案做爲OOP的補充,即便用「切面aspects」封裝橫切關注點以及容許重複使用。最終實現了 AspectJ,就是今天Java開發者仍然使用的一流AOP工具。
也就是說,AOP可不是Spring發明的,Spring只是對AOP作了支持而已。既然如此,AOP裏面的幾個概念就是通用的了。java
《Spring in Action》這本書給出了明確的解釋:spring
在軟件開發中,散佈於應用中多處的功能被稱爲橫切關注點(cross-cutting concern)。一般來說,這些橫切關注點從概念上是與應用的業務邏輯相分離的。好比:日誌、聲明式事物、安全和緩存。這些東西都不是咱們平時寫代碼的核心功能,但許多地方都要用到。
把這些橫切關注點與業務相分離正是面向切面編程(AOP)索要解決的問題。
簡單的說就是把這些許多地方都要用到,但又不是核心業務的功能,單獨剝離出來封裝,經過配置指定要切入到指定的方法中去。sql
如上圖所示,這就是橫切關注點的概念,水平的是核心業務,這些切入的箭頭就是咱們的橫切關注點。express
橫切關注點能夠被模塊化爲特殊的類,這些類被稱爲切面(aspect)。這樣作有兩個好處:編程
首先,如今每一個關注點都集中於一個地方,而不是分割到多處代碼中
其次,服務模塊更簡潔,由於它們只包含主要關注點(或核心功能)的代碼,而次要關注點的代碼被轉移到切面中了。設計模式
通知(Advice):
在AOP中,切面的工做被稱爲通知。通知定義了切面「是什麼」以及「什麼時候」使用。除了描述切面要完成的工做,通知還解決了什麼時候執行這個工做的問題。
Spring切面能夠應用5種類型的通知:api
鏈接點(Join point):
鏈接點是在應用執行過程當中可以插入切面的一個點。這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼能夠利用這些點插入到應用的正常流程之中,並添加行爲。緩存
切點(Pointcut):
若是說通知定義了切面「是什麼」和「什麼時候」的話,那麼切點就定義了「何處」。好比我想把日誌引入到某個具體的方法中,這個方法就是所謂的切點。安全
切面(Aspect):
切面是通知和切點的結合。通知和切點共同定義了切面的所有內容———他是什麼,在什麼時候和何處完成其功能。
引入(Introduction):
引入容許咱們向現有的類添加新的方法和屬性(Spring提供了一個方法注入的功能)。
織入(Weaving):
把切面應用到目標對象來建立新的代理對象的過程,織入通常發生在以下幾個時機:
同依賴注入同樣,Spring AOP也提供兩種配置:
Spring AOP是基於動態代理實現的,代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。至於什麼是動態代理,後文將會解釋。
動態代理只能處理方法,所以Spring AOP只支持方法鏈接點。可是一般方法攔截能夠知足大部分需求。若是須要字段或構造器攔截,別忘了Spring是一個高度可擴展的框架,可使用AspectJ 等第三方AOP框架。
下面就舉兩個例子分別來介紹下Spring AOP的兩種配置方式,咱們就拿簡單的日誌來講明。
@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{ @Override public 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
本段代碼源自嘟嘟的博客,裏面有具體的解釋。
同理,再也不列出詳細代碼。
<aop:aspectj-autoproxy/>
啓用AOP註解。aop
名稱空間的標記描述切面。以下是一個等效的配置:<bean id="logAspect" class="com.springdemo.aspect.LogAspect" /> <aop:config> <aop:aspect id="log" ref="logAspect"> <aop:pointcut id="logAop" expression="execution(* com.springdemo.service.impl.UserServiceImpl.sayHello(..)) and args(name)"/> <aop:before method="logBefore" pointcut-ref="logAop"/> <aop:after method="logAfter" pointcut-ref="logAop"/> <aop:after-returning method="logAfterReturning" pointcut-ref="logAop"/> <aop:after-throwing method="logAfterThrow" pointcut-ref="logAop"/> <!--<aop:around method="logAfterThrow" pointcut-ref="logAop"/>--> </aop:aspect> </aop:config>
參考這篇知乎問答。
首先,靜態代理應該是廣泛瞭解的概念了。它是OOP的一種設計模式,依賴於接口。
動態代理則是爲了解決目標類方法愈來愈多時,代理類也要跟着膨脹的問題。由於動態代理類只要在invoke()
方法中有選擇地實現接口的方法。這對那些廣泛適用的功能來講特別適合,好比緩存、認證、log等等。
public class ProxyHandler implements InvocationHandler { private Object tar; //綁定委託對象,並返回代理類 public Object bind(Object tar) { this.tar = tar; //綁定該類實現的全部接口,取得代理類 return Proxy.newProxyInstance(tar.getClass().getClassLoader(), tar.getClass().getInterfaces(), this); } // invoke中的邏輯能夠對接口中的全部方法生效。 public Object invoke(Object proxy , Method method , Object[] args)throws Throwable { Object result = null; //在調用具體函數方法前,執行功能處理 result = method.invoke(tar,args); //在調用具體函數方法後,執行功能處理 return result; } }
至於InvocationHandler
,它是JDK的java.lang.reflect
名稱空間裏提供的一個類,用來實現動態代理。具體可見Java Dynamic Proxy API。
能夠看出,動態代理的功能十分強大,所以獲得了普遍的應用。好比單元測試中的mock框架,MyBatis的sql註解等等。
最後,動態代理還是須要接口的。而藉助另外一個第三方類庫CGLib,則能夠動態生成字節碼,不依賴於接口。Spring AOP對這兩種技術都有使用。參見CSDN。