AOP(Aspect-Oriented Programming,面向切面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。php
OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP容許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。java
而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即切面。所謂「切面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。git
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用攔截方法的方式,對該方法進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方github
1. 鏈接點(Join point)面試
鏈接點是在應用執行過程當中可以插入切面的一個點。這個點能夠是類的某個方法調用前、調用後、方法拋出異常後等。spring
2. 通知(Advice)編程
在特定的鏈接點,AOP框架執行的動做。後端
Spring AOP 提供了5種類型的通知:數組
3. 切點(Poincut)緩存
具體定位的鏈接點:上面也說了,每一個方法均可以稱之爲鏈接點,咱們具體定位到某一個方法就成爲切點。
切點與鏈接點:切點和鏈接點不是一對一的關係,一個切點匹配多個鏈接點,切點經過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法做爲鏈接點的查詢條件。每一個類都擁有多個鏈接點,例如 ArithmethicCalculator類的全部方法實際上都是鏈接點。
4. 切面(Aspect)
切面由切點和通知組成,它既包括了橫切邏輯的定義、也包括了鏈接點的定義。
5. 織入(Weaving)
織入描述的是把切面應用到目標對象來建立新的代理對象的過程。 Spring AOP 的切面是在運行時被織入,原理是使用了動態代理技術。Spring支持兩種方式生成代理對象:JDK動態代理和CGLib,默認的策略是若是目標類是接口,則使用JDK動態代理技術,不然使用Cglib來生成代理。
6. 引入(Introduction)
添加方法或字段到被通知的類。 Spring容許引入新的接口到任何被通知的對象。例如,你可使用一個引入使任何對象實現 IsModified接口,來簡化緩存。Spring中要使用Introduction, 可有經過DelegatingIntroductionInterceptor來實現通知,經過DefaultIntroductionAdvisor來配置Advice和代理類要實現的接口。
首先新建業務邏輯類,該類實現了基本的除法操做:
public class Calculator { //業務邏輯方法 public int div(int i, int j) { System.out.println("--------"); return i/j; } }
如今須要實現:在div()方法運行以前, 記錄一下日誌, 運行後也記錄一下,運行出異常,也打印一下。
所以可使用AOP來完成日誌的功能,新建日誌切面類:
在定義切面類的時候,須要注意以下幾點:
@Aspect
聲明爲切面類。@Aspect public class LogAspects { @Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))") public void pointCut(){}; //@before表明在目標方法執行前切入, 並指定在哪一個方法前切入 @Before("pointCut()") public void logStart(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法運行....參數列表是:{"+Arrays.asList(joinPoint.getArgs())+"}"); } @After("pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法結束......"); } @AfterReturning(value="pointCut()",returning="result") public void logReturn(Object result){ System.out.println("除法正常返回......運行結果是:{"+result+"}"); } @AfterThrowing(value="pointCut()",throwing="exception") public void logException(Exception exception){ System.out.println("運行異常......異常信息是:{"+exception+"}"); } /*@Around("pointCut()") public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("@Arount:執行目標方法以前..."); Object obj = proceedingJoinPoint.proceed();//至關於開始調div地 System.out.println("@Arount:執行目標方法以後..."); return obj; }*/ }
有了以上操做, 咱們還須要將切面類和被切面的類, 都加入到容器中,注意須要加上@EnableAspectJAutoProxy
開啓AOP。
/* * 日誌切面類的方法須要動態感知到div()方法運行, * 通知方法: * 前置通知:logStart(); 在咱們執行div()除法以前運行(@Before) * 後置通知:logEnd();在咱們目標方法div運行結束以後 ,無論有沒有異常(@After) * 返回通知:logReturn();在咱們的目標方法div正常返回值後運行(@AfterReturning) * 異常通知:logException();在咱們的目標方法div出現異常後運行(@AfterThrowing) * 環繞通知:動態代理, 須要手動執行joinPoint.procced()(其實就是執行咱們的目標方法div,), 執行以前div()至關於前置通知, 執行以後就至關於咱們後置通知(@Around) */ @Configuration @EnableAspectJAutoProxy public class Cap10MainConfig { @Bean public Calculator calculator(){ return new Calculator(); } @Bean public LogAspects logAspects(){ return new LogAspects(); } }
使用JoinPoint能夠拿到相關的內容, 好比方法名, 參數
那麼方法正常返回, 怎麼拿方法的返回值呢?
那麼若是是異常呢?定義
下面是測試程序,注意須要使用從IOC容器中取出Bean,不然直接new對象進行操做,AOP、沒法生效。
從下面的運行結果能夠看到,AOP生效,日誌功能正常:
小結: AOP看起來很麻煩, 只要3步就能夠了:
1, 將業務邏輯組件和切面類都加入到容器中, 告訴spring哪一個是切面類(@Aspect)
2, 在切面類上的每一個通知方法上標註通知註解, 告訴Spring什麼時候運行(寫好切入點表達式,參照官方文檔)
3, 開啓基於註解的AOP模式 @EableXXXX
Spring AOP的實現是基於動態代理,在介紹具體實現細節以前,本節先介紹動態代理的原理。
動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的機制。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在二者之間起到中介的做用(可類比房屋中介,房東委託中介銷售房屋、簽定合同等)。 所謂動態代理,就是實現階段不用關心代理誰,而是在運行階段才指定代理哪一個一個對象(不肯定性)。若是是本身寫代理類的方式就是靜態代理(肯定性)。
不少場景都是利用相似機制作到的,好比用來包裝 RPC 調用、面向切面的編程(AOP)。
(動態)代理模式主要涉及三個要素:
實現方式: 實現動態代理的方式不少,好比 JDK 自身提供的動態代理,就是主要利用了反射機制。還有其餘的實現方式,好比利用字節碼操做機制,相似 ASM、CGLIB(基於 ASM)、Javassist 等。 舉例,常可採用的JDK提供的動態代理接口InvocationHandler來實現動態代理類。其中invoke方法是該接口定義必須實現的,它完成對真實方法的調用。經過InvocationHandler接口,全部方法都由該Handler來進行處理,即全部被代理的方法都由InvocationHandler接管實際的處理任務。此外,咱們常能夠在invoke方法實現中增長自定義的邏輯實現,實現對被代理類的業務邏輯無侵入。
反射機制是 Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。經過反射咱們能夠直接操做類或者對象,好比獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至能夠運行時修改類定義。
代理模式的形式以下圖所示:
代理模式最大的特色就是代理類和實際業務類實現同一個接口(或繼承同一父類),代理對象持有一個實際對象的引用,外部調用時操做的是代理對象,而在代理對象的內部實現中又會去調用實際對象的操做。Java動態代理其實內部也是經過Java反射機制來實現的,即已知的一個對象,而後在運行時動態調用其方法,這樣在調用先後做一些相應的處理。
下面舉例說明:
1. 靜態代理
若代理類在程序運行前就已經存在,那麼這種代理方式被成爲靜態代理 ,這種狀況下的代理類一般都是咱們在Java代碼中定義的。 一般狀況下, 靜態代理中的代理類和委託類會實現同一接口或是派生自相同的父類。
public interface Sell { void sell(); void ad(); }
Vendor的定義以下:
public class Vendor implements Sell{ @Override public void sell() { System.out.println("In sell method"); } @Override public void ad() { System.out.println("ad method"); } }
BusinessAgent的定義以下:
/** * 靜態代理,經過聚合來實現,讓代理類有一個委託類的引用便可。 * */ public class BusinessAgent implements Sell{ private Sell vendor; public BusinessAgent(Sell vendor) { this.vendor = vendor; } @Override public void sell() { // 一些業務邏輯 System.out.println("before sell"); vendor.sell(); System.out.println("after sell"); } @Override public void ad() { // 一些業務邏輯 System.out.println("before ad"); vendor.ad(); System.out.println("after ad"); } }
由上面的代碼能夠看到, 經過靜態代理,一方面無需修改Vendor的代碼就能夠加入一些業務處理邏輯;另外一方面,實現了客戶端與委託類的解耦。但這種靜態代理的侷限在於,必須在運行前編寫好代理類,若是委託類的方法較多,在添加業務邏輯時的工做量較大,須要對每一個方法單獨添加。
2. 動態代理
代理類在程序運行時建立的代理方式被成爲 動態代理。 也就是說,這種狀況下,代理類並非在Java代碼中定義的,而是在運行時根據咱們在Java代碼中的「指示」動態生成的。相比於靜態代理, 動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類的函數。
一樣仍是上面的例子,須要在委託類的每一個方法先後加入一些處理邏輯,在動態代理的實現中,首先須要定義一個位於代理類與委託類之間的中介類,這個中介類被要求實現InvocationHandler接口,這個接口的定義以下:
/** * 調用處理程序 * 代理類對象做爲proxy參數傳入,參數method標識了咱們具體調用的是代理類的哪一個方法,args爲這個方法的參數 */ public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args); }
中介類必須實現InvocationHandler接口,做爲調用處理器」攔截「對代理類方法的調用。
public class DynamicProxy implements InvocationHandler { // obj爲委託對象 private Object object; public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(object, args); System.out.println("after"); return result; } }
在使用時須要動態生成代理類,具體以下:
public class Main { public static void main(String[] args) { /* Static proxy */ Vendor vendor = new Vendor(); BusinessAgent businessAgent = new BusinessAgent(vendor); businessAgent.sell(); businessAgent.ad(); /* Dynamic proxy */ DynamicProxy inter = new DynamicProxy(new Vendor()); //加上這句將會產生一個$Proxy0.class文件,這個文件即爲動態生成的代理類文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // 獲取代理實例sell /** * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) * throws IllegalArgumentException * loader:定義了代理類的ClassLoder; * interfaces:代理類實現的接口列表; * h:調用處理器,也就是咱們上面定義的實現了InvocationHandler接口的類實例. */ Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter)); // 經過代理類對象調用代理方法,實際上會轉到invoke方法調用 sell.sell(); sell.ad(); } }
總結: 動態代理的原理就是,首先經過newProxyInstance方法獲取代理類實例,然後咱們即可以經過這個代理類實例調用代理類的方法,對代理類的方法的調用實際上都會調用中介類(調用處理器)的invoke方法,在invoke方法中咱們調用委託類的相應方法,而且能夠添加本身的處理邏輯。
CGLIB(Code Generation Library)是一個基於ASM的字節碼生成庫,它容許咱們在運行時對字節碼進行修改和動態生成。CGLIB經過繼承方式實現代理。
來看示例,假設咱們有一個沒有實現任何接口的類HelloConcrete:
public class HelloConcrete { public String sayHello(String str) { return "HelloConcrete: " + str; } }
由於沒有實現接口該類沒法使用JDK代理,經過CGLIB代理實現以下:
// CGLIB動態代理 // 1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。 class MyMethodInterceptor implements MethodInterceptor{ ... @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { logger.info("You said: " + Arrays.toString(args)); return proxy.invokeSuper(obj, args); } } // 2. 而後在須要使用HelloConcrete的時候,經過CGLIB動態代理獲取代理對象。 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloConcrete.class); enhancer.setCallback(new MyMethodInterceptor()); HelloConcrete hello = (HelloConcrete)enhancer.create(); System.out.println(hello.sayHello("I love you!")); // 輸出結果以下 // 日誌信息: You said: [I love you!] // HelloConcrete: I love you!
經過CGLIB的Enhancer來指定要代理的目標對象、實際處理代理邏輯的對象,最終經過調用create()方法獲得代理對象,對這個對象全部非final方法的調用都會轉發給MethodInterceptor.intercept()方法,在intercept()方法裏咱們能夠加入任何邏輯,好比修改方法參數,加入日誌功能、安全檢查功能等;經過調用MethodProxy.invokeSuper()方法,咱們將調用轉發給原始對象,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的做用跟JDK代理中的InvocationHandler很相似,都是方法調用的中轉站。
CGLIB是經過繼承(
如上面例子中的enhancer.setSuperclass(HelloConcrete.class)
)實現代理,因爲final類型不能有子類,因此CGLIB不能代理final類型,遇到這種狀況會拋出異常。
AOP的原理簡單來說,利用動態代理,在IOC容器初始化時,建立Bean的代理類;在代理方法被調用時,代理類會攔截方法的調用,並在以前或者以後插入切面方法,以此實現AOP的目標。
接下來會從如下幾方面深刻分析AOP的原理:
在以前使用AOP時,爲了啓用AOP,須要在配置類中,聲明@EnableAspectJAutoProxy
的註解,這個註解的功能就是註冊AnnotationAwareAspectJAutoProxyCreator
。下面具體分析這個組件是如何註冊的。
@w=250
進入@EnableAspectJAutoProxy
的源碼中,能夠看到該類引入了AspectJAutoProxyRegistrar
,
@Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { //proxyTargetClass屬性,默認false,採用JDK動態代理織入加強(實現接口的方式);若是設爲true,則採用CGLIB動態代理織入加強 boolean proxyTargetClass() default false; //經過aop框架暴露該代理對象,aopContext可以訪問 boolean exposeProxy() default false; }
在AspectJAutoProxyRegistrar
中, 能夠看到實現了ImportBeanDefinitionRegistrar接口,這個接口以前也有介紹,能給容器中自定義註冊組件。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { /** * Register, escalate, and configure the AspectJ auto proxy creator based on the value * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing * {@code @Configuration} class. */ @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } }
重點關注AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
,這一步將會註冊AnnotationAwareAspectJAutoProxyCreator
。下面進入源碼,
程序的邏輯很清晰,
綜上分析,@EnableAspectJAutoProxy的功能就是,利用其中的AspectJAutoProxyRegistrar給咱們容器中註冊一個AnnotationAwareAspectJAutoProxyCreator組件,這是後續建立加強Bean的基礎。
AnnotationAwareAspectJAutoProxyCreator的類層次結構以下圖所示,
繼承關係爲:
其中的SmartInstantiationAwareBeanPostProcessor
是Bean的後置處理器,同時也實現了BeanFactoryAware
能夠在容器初始化時,將beanFactory傳進來進行相關操做。
由上述分析可知,AnnotationAwareAspectJAutoProxyCreator既具備BeanPostProcessor特色, 也實現了BeanFactoryAware接口,方便操做BeanFactory。
上面已經介紹了AOP過程當中,核心的AnnotationAwareAspectJAutoProxyCreator組件,接下來對整個AOP的流程進行梳理,主要分爲以下4個步驟:
註冊AnnotationAwareAspectJAutoProxyCreator
的BeanDefinition
建立AnnotationAwareAspectJAutoProxyCreator
,並加入到BeanFactory
利用AnnotationAwareAspectJAutoProxyCreator
攔截Bean的初始化,建立加強的Bean
加強Bean的調用過程
IOC容器初始化的入口是以下的refresh()函數,上面1,2,3步驟,分別發生在以下標出的3個函數中,下面分別對這三個函數進行詳細介紹。
1. 註冊AnnotationAwareAspectJAutoProxyCreator
的BeanDefinition
這一步主要是經過invokeBeanFactoryPostProcessors(beanFactory)
函數,添加AnnotationAwareAspectJAutoProxyCreator
的定義,最終調用的函數以下:
註冊的組件類型爲AnnotationAwareAspectJAutoProxyCreator.class,組件名稱ATUO_PROXY_CREATOR_BEAN_NAME值爲internalAutoProxyCreator。
下面是調用棧:
2. 建立AnnotationAwareAspectJAutoProxyCreator
,並加入到BeanFactory
這一步入口是registerBeanPostProcessors(beanFactory)
,進入該函數後,會跳轉到以下的核心函數中進行beanPostProcess的實例化。注意到以前提到過,AnnotationAwareAspectJAutoProxyCreator
實現了BeanPostProcess接口,因此能夠將其當成一個正常的後置處理器來進行實例化。
從下面的debug信息能夠看到,在這一步中,容器須要實例化4個後置處理器,其中最後一個就是咱們關注的AnnotationAwareAspectJAutoProxyCreator
。
整個初始化後置處理器的流程,能夠分爲以下幾步:
1)先獲取ioc容器已經定義了的須要建立對象的全部BeanPostProcessor
3)優先註冊實現了PriorityOrdered接口的BeanPostProcessor;
4)再給容器中註冊實現了Ordered接口的BeanPostProcessor;
5)註冊沒實現優先級接口的BeanPostProcessor;
後置處理器AnnotationAwareAspectJAutoProxyCreator
實例化完成以後,在接下來的Bean的實例化過程當中,它會去嘗試攔截Bean的初始化,若是有須要,則會建立代理加強後的Bean。
3. 利用AnnotationAwareAspectJAutoProxyCreator
攔截Bean的初始化,建立加強的Bean
在以前的例子中,定義了以下的切面類,實現了相關的advice方法。
這是Calculate類,就是須要加強的類。
@w=300
這一步中主要關注這兩個Bean的實例化。
這一步的入口是refresh函數中的beanFactory.preInstantiateSingletons()
,下一步進入到getBean-->doGetBean
函數,
接着進入doGetBean-->createBean
函數,
接着進入到createBean
函數,會調用函數Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
試圖直接返回proxy對象。
接下來首先分析這個函數,再分析以後正常的初始化流程。createBean
函數是理解整個AOP流程的核心。
進入到函數的實現,能夠看到最後會去嘗試調用類型爲InstantiationAwareBeanPostProcessor
的後置處理器,因爲AnnotationAwareAspectJAutoProxyCreator
實現了該接口,因此這個時候會被調用來試圖返回proxy對象,可是一般狀況下,加強bean不會在這裏生成。
但並非說這個AnnotationAwareAspectJAutoProxyCreator
就沒有做用,進入到該函數的實現,能夠發如今shouldSkip
函數中會去找到全部的Advisor,也就是以前例子中的LogAspects
類,並把這些Advisor放到BeanFactory中,方便後續建立加強的Bean。
在獲取到全部的Advisor以後,判斷當前bean是否在advisedBeans中(保存了全部須要加強bean)
以及判斷當前bean是不是基礎類型的Advice、Pointcut、Advisor、AopInfrastructureBean,若是是的話就跳過。
回到createBean
函數,下面進入到正常的Bean初始化流程,一步步跟進到initializeBean函數中,能夠看到在初始化Bean的先後都會調用對應的後置處理器來完成相應的功能,可是AbstractAutoProxyCreator的實現中,在初始化Bean以前,只是直接返回Bean;可是在初始化完Bean以後,會調用對應的後置處理器,也就是在applyBeanPostProcessorsAfterInitialization
函數中來建立加強的Bean。
下面對該函數進行仔細分析,
接着分析createProxy
函數的實現,下面省略了部分中間調用,在最後的實現中,createAopProxy會根據狀況使用jdk代理或者CGLib,從代碼中能夠看到,當被代理類是接口或者是proxy類時,就會採用jdk動態代理,反之則採用CGLib。
之後容器中獲取到的就是這個組件的代理對象,執行目標方法的時候,代理對象就會執行通知方法的流程;
注意一點:在createAopProxy時,會判斷config.isProxyTargetClass(),這個值默認爲false。可是在兩個地方進行設置,一個是EnableAspectJAutoProxy註解中,另外一個地方是在createProxy函數中,evaluateProxyInterfaces會去查找目標類的全部interface,若是可用的話,則將其加到proxyFactory中,不然,調用setProxyTargetClass,設置爲true。在本例子中,calculate類沒有相關接口,因此設置爲true。這也是爲何在createAopProxy函數中,會進行判斷,而不是直接返回jdk動態代理的類。
4. 加強Bean的調用過程
上面對AOP流程進行了梳理,經過代碼分析瞭如何代理生成加強的Bean。這部分介紹在調用加強Bean的方法時,proxy對象是如何攔截方法調用的。
當被加強的Bean在執行時,會進入到下面的攔截執行流程,
首先,根據ProxyFactory對象獲取將要執行的目標方法攔截器鏈:List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
進一步跟進到實現,getInterceptorsAndDynamicInterceptionAdvice
的流程大體以下,主要是爲了獲取攔截鏈:
在獲得攔截鏈以後,若是沒有攔截器鏈,直接執行目標方法;若是有攔截器鏈,把須要執行的目標對象,目標方法,攔截器鏈等信息傳入建立一個 CglibMethodInvocation 對象,並調用 mi.proceed();
來獲取執行結果。
注意:攔截器鏈的觸發過程是一個迭代的過程,
從下面的調用棧能夠看到,全部的攔截器都會等待下一個攔截器調用完成後,再接着執行。
當在執行Before
方法時,會先執行完before定義好的方法,而後再去執行正常的方法體:
整個攔截的流程能夠總結以下圖所示:
下面對整個AOP實現流程進行總結:
a. 建立業務邏輯組件和切面組件
b. AnnotationAwareAspectJAutoProxyCreator攔截組件的建立過程
c. 組件建立完以後,判斷組件是否須要加強,若是是,則將切面的通知方法,包裝成加強器(Advisor);給業務邏輯組件建立一個代理對象(cglib);
a. 獲得目標方法的攔截器鏈(加強器包裝成攔截器MethodInterceptor)
b. 利用攔截器的鏈式機制,依次進入每個攔截器進行執行;
c. 效果:
正常執行:前置通知-》目標方法-》後置通知-》返回通知
出現異常:前置通知-》目標方法-》後置通知-》異常通知
以前介紹的都是標準的Spring AOP實現,經過在運行時對目標類加強,生成代理類。可是利用AspectJ一樣能夠實現加強,只是後者是編譯時加強,並且與Spring框架沒有關係,能夠獨立運行。
下面先簡單介紹AspectJ的使用,而後將其與Spring AOP進行對比。
業務組件 SayHelloService package com.ywsc.fenfenzhong.aspectj.learn; public class SayHelloService { public void say(){ System.out.print("Hello AspectJ"); } }
須要來了,在須要在調用say()方法以後,須要記錄日誌。那就是經過AspectJ的後置加強吧。
LogAspect 日誌記錄組件,實現對SayHelloService 後置加強 public aspect LogAspect{ pointcut logPointcut():execution(void SayHelloService.say()); after():logPointcut(){ System.out.println("記錄日誌 ..."); } }
執行命令 ajc -d . SayHelloService.java LogAspect.java 生成 SayHelloService.class 執行命令 java SayHelloService 輸出 Hello AspectJ 記錄日誌
ajc.exe 能夠理解爲 javac.exe 命令,都用於編譯 Java 程序,區別是 ajc.exe 命令可識別 AspectJ 的語法;咱們能夠將 ajc.exe 當成一個加強版的 javac.exe 命令.執行ajc命令後的 SayHelloService.class 文件不是由原來的 SayHelloService.java 文件編譯獲得的,該 SayHelloService.class 裏新增了打印日誌的內容——這代表 AspectJ 在編譯時「自動」編譯獲得了一個新類,這個新類加強了原有的 SayHelloService.java 類的功能,所以 AspectJ 一般被稱爲編譯時加強的 AOP 框架。
1. 從目標角度講:
2. 織入(Weaving)
AspectJ利用了下面3種不一樣的織入方法:
相比於AspectJ,Spring AOP利用了運行時織入(runtime weaving)。
經過動態織入,切面方法被動態的織入到程序的運行過程當中,一般有JDK動態代理或者CGLIB代理。
Spring AOP傾向於使用JDK動態代理,只要目標對象實現了至少一個接口,Spring將會採用JDK動態代理來建立加強的Bean。
若是目標方法沒有實現接口,就會採用CGLIB來實現。
3. 鏈接點(Join point)
從設計的角度講,Spring AOP經過代理模式來實現, 例如CGLIB建立目標類的子類(以下圖的實例所示),再調父類的目標方法實現AOP。可是,一旦目標父類使用了關鍵字final,子類沒法繼承,切入就不能實現。所以不能代理final類型,一樣的,也不能代理static方法,由於他們不能被重寫。因此一般狀況下,Spring AOP只支持方法做爲鏈接點。
AspectJ沒有這種限制,在編譯期直接將加強方法織入到代碼中,也不須要像Spring AOP那樣繼承目標方法,所以能夠支持更多的鏈接點。
具體比較以下:
總結:
Spring AOP是基於代理的實現方式,在程序運行時建立代理,並經過攔截鏈來執行切面方法。AspectJ在編譯期將切面方法織入到目標類,在運行期沒有其餘性能損耗,所以性能上相比Spring AOP會快不少。
下表是一個總體的對比:
參考:
本文由『後端精進之路』原創,首發於博客 http://teckee.github.io/ , 轉載請註明出處
搜索『後端精進之路』關注公衆號,馬上獲取最新文章和價值2000元的BATJ精品面試課程。