淺談Spring AOP 實現原理

什么是AOP

AOP(Aspect-OrientedProgramming,面向方面編程)余耽,可以說是OOP(Object-Oriented Programing缚柏,面向對象編程)的補充和完善。OOP引入封裝碟贾、繼承和多態(tài)性等概念來建立一種對象層次結構币喧,用以模擬公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候袱耽,OOP則顯得無能為力杀餐。也就是說,OOP允許你定義從上到下的關系朱巨,但并不適合定義從左到右的關系史翘。例如日志功能。日志代碼往往水平地散布在所有對象層次中冀续,而與它所散布到的對象的核心功能毫無關系琼讽。對于其他類型的代碼,如安全性洪唐、異常處理和透明的持續(xù)性也是如此钻蹬。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中凭需,它導致了大量代碼的重復问欠,而不利于各個模塊的重用肝匆。

而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術溅潜,剖解開封裝的對象內部术唬,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其名為“Aspect”滚澜,即方面粗仓。所謂“方面”,簡單地說设捐,就是將那些與業(yè)務無關借浊,卻為業(yè)務模塊所共同調用的邏輯或責任封裝起來,便于減少系統(tǒng)的重復代碼萝招,降低模塊間的耦合度蚂斤,并有利于未來的可操作性和可維護性。AOP代表的是一個橫向的關系槐沼,如果說“對象”是一個空心的圓柱體曙蒸,其中封裝的是對象的屬性和行為;那么面向方面編程的方法岗钩,就仿佛一把利刃纽窟,將這些空心圓柱體剖開,以獲得其內部的消息兼吓。而剖開的切面臂港,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開的切面復原视搏,不留痕跡审孽。

使用“橫切”技術,AOP把軟件系統(tǒng)分為兩個部分:核心關注點和橫切關注點浑娜。業(yè)務處理的主要流程是核心關注點佑力,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是筋遭,他們經常發(fā)生在核心關注點的多處搓萧,而各處都基本相似。比如權限認證宛畦、日志瘸洛、事務處理。Aop 的作用在于分離系統(tǒng)中的各種關注點次和,將核心關注點和橫切關注點分離開來反肋。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業(yè)邏輯同對其提供支持的通用服務進行分離踏施∈幔”

實現AOP的技術罕邀,主要分為兩大類:一是采用動態(tài)代理技術,利用截取消息的方式养距,對該消息進行裝飾诉探,以取代原有對象行為的執(zhí)行;二是采用靜態(tài)織入的方式棍厌,引入特定的語法創(chuàng)建“方面”肾胯,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。

AOP使用場景

AOP用來封裝橫切關注點耘纱,具體可以在下面的場景中使用:

  1. Authentication 權限
  2. Caching 緩存
  3. Context passing 內容傳遞
  4. Error handling 錯誤處理
  5. Lazy loading 懶加載
  6. Debugging  調試
  7. logging, tracing, profiling and monitoring 記錄跟蹤 優(yōu)化 校準
  8. Performance optimization 性能優(yōu)化
  9. Persistence  持久化
  10. Resource pooling 資源池
  11. Synchronization 同步
  12. Transactions 事務

AOP相關概念

方面(Aspect):一個關注點的模塊化敬肚,這個關注點實現可能另外橫切多個對象。事務管理是J2EE應用中一個很好的橫切關注點例子束析。方面用Spring的 Advisor或攔截器實現艳馒。

連接點(Joinpoint): 程序執(zhí)行過程中明確的點,如方法的調用或特定的異常被拋出员寇。

通知(Advice): 在特定的連接點弄慰,AOP框架執(zhí)行的動作。各種類型的通知包括“around”蝶锋、“before”和“throws”通知曹动。通知類型將在下面討論。許多AOP框架包括Spring都是以攔截器做通知模型牲览,維護一個“圍繞”連接點的攔截器鏈。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入點(Pointcut): 指定一個通知將被引發(fā)的一系列連接點的集合恶守。AOP框架必須允許開發(fā)者指定切入點:例如第献,使用正則表達式。 Spring定義了Pointcut接口兔港,用來組合MethodMatcher和ClassFilter庸毫,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知衫樊,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上

引入(Introduction): 添加方法或字段到被通知的類飒赃。 Spring允許引入新的接口到任何被通知的對象。例如科侈,你可以使用一個引入使任何對象實現 IsModified接口载佳,來簡化緩存。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知臀栈,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的接口

目標對象(Target Object): 包含連接點的對象蔫慧。也被稱作被通知或被代理對象。POJO

AOP代理(AOP Proxy): AOP框架創(chuàng)建的對象权薯,包含通知姑躲。 在Spring中睡扬,AOP代理可以是JDK動態(tài)代理或者CGLIB代理。

織入(Weaving): 組裝方面來創(chuàng)建一個被通知對象黍析。這可以在編譯時完成(例如使用AspectJ編譯器)卖怜,也可以在運行時完成。Spring和其他純Java AOP框架一樣阐枣,在運行時完成織入马靠。

Spring AOP組件

下面這種類圖列出了Spring中主要的AOP組件


如何使用Spring AOP

可以通過配置文件或者編程的方式來使用Spring AOP。

配置可以通過xml文件來進行侮繁,大概有四種方式:

  1. 配置ProxyFactoryBean虑粥,顯式地設置advisors, advice, target等

  2. 配置AutoProxyCreator,這種方式下宪哩,還是如以前一樣使用定義的bean娩贷,但是從容器中獲得的其實已經是代理對象

  3. 通過<aop:config>來配置

  4. 通過<aop: aspectj-autoproxy>來配置,使用AspectJ的注解來標識通知及切入點

也可以直接使用ProxyFactory來以編程的方式使用Spring AOP锁孟,通過ProxyFactory提供的方法可以設置target對象, advisor等相關配置彬祖,最終通過 getProxy()方法來獲取代理對象

具體使用的示例可以google. 這里略去

Spring AOP代理對象的生成

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定品抽。默認的策略是如果目標類是接口储笑,則使用JDK動態(tài)代理技術,否則使用Cglib來生成代理圆恤。下面我們來研究一下Spring如何使用JDK來生成代理對象突倍,具體的生成代碼放在JdkDynamicAopProxy這個類中,直接上相關代碼:

/**
    * <ol>
    * <li>獲取代理類要實現的接口,除了Advised對象中配置的,還會加上SpringProxy, Advised(opaque=false)
    * <li>檢查上面得到的接口中有沒有定義 equals或者hashcode的接口
    * <li>調用Proxy.newProxyInstance創(chuàng)建代理對象
    * </ol>
    */
   public Object getProxy(ClassLoader classLoader) {
       if (logger.isDebugEnabled()) {
           logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
       }
       Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
       findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

那這個其實很明了盆昙,注釋上我也已經寫清楚了羽历,不再贅述。

下面的問題是淡喜,代理對象生成了秕磷,那切面是如何織入的?

我們知道InvocationHandler是JDK動態(tài)代理的核心炼团,生成的代理對象的方法調用都會委托到InvocationHandler.invoke()方法澎嚣。而通過JdkDynamicAopProxy的簽名我們可以看到這個類其實也實現了InvocationHandler,下面我們就通過分析這個類中實現的invoke()方法來具體看下Spring AOP是如何織入切面的瘟芝。

publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
       MethodInvocation invocation = null;
       Object oldProxy = null;
       boolean setProxyContext = false;
 
       TargetSource targetSource = this.advised.targetSource;
       Class targetClass = null;
       Object target = null;
 
       try {
           //eqauls()方法易桃,具目標對象未實現此方法
           if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
                return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
           }
 
           //hashCode()方法,具目標對象未實現此方法
           if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
                return newInteger(hashCode());
           }
 
           //Advised接口或者其父接口中定義的方法,直接反射調用,不應用通知
           if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
                    &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                // Service invocations onProxyConfig with the proxy config...
                return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
           }
 
           Object retVal = null;
 
           if (this.advised.exposeProxy) {
                // Make invocation available ifnecessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
           }
 
           //獲得目標對象的類
           target = targetSource.getTarget();
           if (target != null) {
                targetClass = target.getClass();
           }
 
           //獲取可以應用到此方法上的Interceptor列表
           List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
 
           //如果沒有可以應用到此方法的通知(Interceptor)锌俱,此直接反射調用 method.invoke(target, args)
           if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
           } else {
                //創(chuàng)建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
           }
 
           // Massage return value if necessary.
           if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
                    &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                // Special case: it returned"this" and the return type of the method
                // is type-compatible. Notethat we can't help if the target sets
                // a reference to itself inanother returned object.
                retVal = proxy;
           }
           return retVal;
       } finally {
           if (target != null && !targetSource.isStatic()) {
                // Must have come fromTargetSource.
               targetSource.releaseTarget(target);
           }
           if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
           }
       }
    }

主流程可以簡述為:獲取可以應用到此方法上的通知鏈(Interceptor Chain),如果有,則應用通知,并執(zhí)行joinpoint; 如果沒有,則直接反射執(zhí)行joinpoint颈抚。而這里的關鍵是通知鏈是如何獲取的以及它又是如何執(zhí)行的,下面逐一分析下。

首先贩汉,從上面的代碼可以看到驱富,通知鏈是通過Advised.getInterceptorsAndDynamicInterceptionAdvice()這個方法來獲取的,我們來看下這個方法的實現:

public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
                   MethodCacheKeycacheKey = new MethodCacheKey(method);
                   List<Object>cached = this.methodCache.get(cacheKey);
                   if(cached == null) {
                            cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                                               this,method, targetClass);
                            this.methodCache.put(cacheKey,cached);
                   }
                   returncached;
         }

可以看到實際的獲取工作其實是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()這個方法來完成的,獲取到的結果會被緩存匹舞。

說到這里順便給大家推薦一個Java架構方面的交流學習群:956011797褐鸥,點擊立即加入里面會免費分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis赐稽,Netty源碼分析叫榕,高并發(fā)、高性能姊舵、分布式晰绎、微服務架構的原理,JVM性能優(yōu)化這些成為架構師必備的知識體系括丁。還能領取免費的學習資源和前輩的面試經驗和面試題荞下,相信對于已經工作和遇到技術瓶頸的碼友,在這個群里會有你需要的內容史飞。
下面來分析下這個方法的實現:

/**
    * 從提供的配置實例config中獲取advisor列表,遍歷處理這些advisor.如果是IntroductionAdvisor,
    * 則判斷此Advisor能否應用到目標類targetClass上.如果是PointcutAdvisor,則判斷
    * 此Advisor能否應用到目標方法method上.將滿足條件的Advisor通過AdvisorAdaptor轉化成Interceptor列表返回.
    */
    publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
       // This is somewhat tricky... we have to process introductions first,
       // but we need to preserve order in the ultimate list.
       List interceptorList = new ArrayList(config.getAdvisors().length);
 
       //查看是否包含IntroductionAdvisor
       boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
 
       //這里實際上注冊一系列AdvisorAdapter,用于將Advisor轉化成MethodInterceptor
       AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
 
       Advisor[] advisors = config.getAdvisors();
        for (int i = 0; i <advisors.length; i++) {
           Advisor advisor = advisors[i];
           if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
                if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                    //TODO: 這個地方這兩個方法的位置可以互換下
                    //將Advisor轉化成Interceptor
                    MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
 
                    //檢查當前advisor的pointcut是否可以匹配當前方法
                    MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
 
                    if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
                        if(mm.isRuntime()) {
                            // Creating a newobject instance in the getInterceptors() method
                            // isn't a problemas we normally cache created chains.
                            for (intj = 0; j < interceptors.length; j++) {
                               interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
                            }
                        } else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
           } else if (advisor instanceof IntroductionAdvisor){
                IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
                if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
                    Interceptor[] interceptors= registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
           } else {
                Interceptor[] interceptors =registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
           }
       }
       return interceptorList;
}

這個方法執(zhí)行完成后尖昏,Advised中配置能夠應用到連接點或者目標類的Advisor全部被轉化成了MethodInterceptor.

接下來我們再看下得到的攔截器鏈是怎么起作用的。

if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
            } else {
                //創(chuàng)建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

從這段代碼可以看出构资,如果得到的攔截器鏈為空抽诉,則直接反射調用目標方法,否則創(chuàng)建MethodInvocation吐绵,調用其proceed方法迹淌,觸發(fā)攔截器鏈的執(zhí)行,來看下具體代碼

public Object proceed() throws Throwable {
       //  We start with an index of -1and increment early.
       if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
           //如果Interceptor執(zhí)行完了己单,則執(zhí)行joinPoint
           return invokeJoinpoint();
       }
 
       Object interceptorOrInterceptionAdvice =
           this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
       
       //如果要動態(tài)匹配joinPoint
       if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
           // Evaluate dynamic method matcher here: static part will already have
           // been evaluated and found to match.
           InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
           //動態(tài)匹配:運行時參數是否滿足匹配條件
           if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
                //執(zhí)行當前Intercetpor
                returndm.interceptor.invoke(this);
           }
           else {
                //動態(tài)匹配失敗時,略過當前Intercetpor,調用下一個Interceptor
                return proceed();
           }
       }
       else {
           // It's an interceptor, so we just invoke it: The pointcutwill have
           // been evaluated statically before this object was constructed.
           //執(zhí)行當前Intercetpor
           return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
       }
}

代碼也比較簡單唉窃,這里不再贅述。

讀者福利

針對于上面的文章我總結出了互聯網公司java程序員面試涉及到的絕大部分面試題及答案做成了文檔和架構視頻資料免費分享給大家(包括Dubbo荷鼠、Redis、Netty榔幸、zookeeper允乐、Spring cloud、分布式削咆、高并發(fā)等架構技術資料)牍疏,希望能幫助到您面試前的復習且找到一個好的工作,也節(jié)省大家在網上搜索資料的時間來學習拨齐。

資料獲取方式:加qun群:956011797點擊立即加入 找管理小姐姐免費獲攘墼伞!

合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰厦滤!趁年輕援岩,使勁拼,給未來的自己一個交代掏导!



?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末享怀,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子趟咆,更是在濱河造成了極大的恐慌添瓷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件值纱,死亡現場離奇詭異鳞贷,居然都是意外死亡,警方通過查閱死者的電腦和手機虐唠,發(fā)現死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門搀愧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凿滤,你說我怎么就攤上這事妈橄。” “怎么了翁脆?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵眷蚓,是天一觀的道長。 經常有香客問我反番,道長沙热,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任罢缸,我火速辦了婚禮篙贸,結果婚禮上,老公的妹妹穿的比我還像新娘枫疆。我一直安慰自己爵川,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布息楔。 她就那樣靜靜地躺著寝贡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪值依。 梳的紋絲不亂的頭發(fā)上圃泡,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音愿险,去河邊找鬼颇蜡。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的风秤。 我是一名探鬼主播鳖目,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唁情!你這毒婦竟也來了疑苔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤甸鸟,失蹤者是張志新(化名)和其女友劉穎惦费,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體抢韭,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡薪贫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了刻恭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞧省。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鳍贾,靈堂內的尸體忽然破棺而出鞍匾,到底是詐尸還是另有隱情,我是刑警寧澤骑科,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布橡淑,位于F島的核電站,受9級特大地震影響咆爽,放射性物質發(fā)生泄漏梁棠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一斗埂、第九天 我趴在偏房一處隱蔽的房頂上張望符糊。 院中可真熱鬧,春花似錦呛凶、人聲如沸男娄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽模闲。三九已至,卻和暖如春县好,著一層夾襖步出監(jiān)牢的瞬間围橡,已是汗流浹背暖混。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工缕贡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓晾咪,卻偏偏與公主長得像收擦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谍倦,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容