摘要:何為簡單點來定義就是切面,是一種編程范式。定義一個切面的載體定義一個切點定義一個為,并指定對應的切點一個注冊配置類,啟動容器,初始化時期獲取對象,獲取對象時期,并進行打印好了,這樣我們整體的代理就已經完成。
問題:Spring AOP代理中的運行時期,是在初始化時期織入還是獲取對象時期織入?
織入就是代理的過程,指目標對象進行封裝轉換成代理,實現了代理,就可以運用各種代理的場景模式。何為AOP
簡單點來定義就是切面,是一種編程范式。與OOP對比,它是面向切面,為何需要切面,在開發中,我們的系統從上到下定義的模塊中的過程中會產生一些橫切性的問題,這些橫切性的問題和我們的主業務邏輯關系不大,假如不進行AOP,會散落在代碼的各個地方,造成難以維護。AOP的編程思想就是把業務邏輯和橫切的問題進行分離,從而達到解耦的目的,使代碼的重用性、侵入性低、開發效率高。
AOP使用場景日志記錄;記錄調用方法的入參和結果返參。
用戶的權限驗證;驗證用戶的權限放到AOP中,與主業務進行解耦。
性能監控;監控程序運行方法的耗時,找出項目的瓶頸。
事務管理;控制Spring事務,Mysql事務等。
AOP概念點 AOP和Spring AOP的關系在這里問題中,也有一個類似的一對IOC和DI(dependency injection)的關系,AOP可以理解是一種編程目標,Spring AOP就是這個實現這個目標的一種手段。同理IOC也是一種編程目標,DI就是它的一個手段。
SpringAOP和AspectJ是什么關系在Spring官網可以看到,AOP的實現提供了兩種支持分別為@AspectJ、Schema-based AOP。其實在Spring2.5版本時,Spring自己實現了一套AOP開發的規范和語言,但是這一套規范比較復雜,可讀性差。之后,Spring借用了AspectJ編程風格,才有了@AspectJ的方式支持,那么何為編程風格。
Annotation注解方式;對應@AspectJ
JavaConfig;對應Schema-based AOP
SpringAOP和AspectJ的詳細對比,在之后的章節會在進行更加詳細的說明,將會在他們的背景、織入方法、性能做介紹。
Spring AOP的應用閱讀官網,是我們學習一個新知識的最好途徑,這個就是Spring AOP的核心概念點,跟進它們的重要性,我做了重新的排序,以便好理解,這些會為我們后續的源碼分析起到作用。
Aspect:切面;使用@Aspect注解的Java類來實現,集合了所有的切點,做為切點的一個載體,做一個比喻就像是我們的一個數據庫。
Tips:這個要實現的話,一定要交給Spirng IOC去管理,也就是需要加入@Component。
Pointcut:切點;表示為所有Join point的集合,就像是數據庫中一個表。
Join point:連接點;俗稱為目標對象,具體來說就是servlet中的method,就像是數據庫表中的記錄。
Advice:通知;這個就是before、after、After throwing、After (finally)。
Weaving:把代理邏輯加入到目標對象上的過程叫做織入。
target:目標對象、原始對象。
aop Proxy:代理對象 包含了原始對象的代碼和增加后的代碼的那個對象。
TipsSpringAOP源碼分析
這個應用點,有很多的知識點可以讓我們去挖掘,比如Pointcut中execution、within的區別,我相信你去針對性搜索或者官網都未必能有好的解釋,稍后會再專門挑一個文章做重點的使用介紹;
為了回答我們的一開始的問題,前面的幾個章節我們做了一些簡單的概念介紹做為鋪墊,那么接下來我們回歸正題,正面去切入問題。以碼說話,我們以最簡潔的思路把AOP實現,我們先上代碼。
項目結構介紹
項目目錄結構,比較簡單,5個主要的文件;
pom.xml核心代碼;spring-content是核心jar,已經包含了spring所有的基礎jar,aspectjweaver是為了實現AOP。
AppConfig.java;定義一個Annotation,做為我們Spirng IOC容器的啟動類。
package com.will.config; ? @Configuration @ComponentScan("com.will") @EnableAspectJAutoProxy(proxyTargetClass = false) public class AppConfig { }
WilAspect.java ;按照官網首推的方式(@AspectJ support),實現AOP代理。
package com.will.config; ? /** * 定義一個切面的載體 */ @Aspect @Component public class WilAspect { /** * 定義一個切點 */ @Pointcut("execution(* com.will.dao.*.*(..))") public void pointCutExecution(){ } ? /** * 定義一個Advice為Before,并指定對應的切點 * @param joinPoint */ @Before("pointCutExecution()") public void before(JoinPoint joinPoint){ System.out.println("proxy-before"); } }
Dao.java
package com.will.dao; public interface Dao { public void query(); }
UserDao.java
package com.will.dao; import org.springframework.stereotype.Component; @Component public class UserDao implements Dao { public void query() { System.out.println("query user"); } }
Test.java
package com.will.test; import com.will.config.AppConfig; import com.will.dao.Dao; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { /** * new一個注冊配置類,啟動IOC容器,初始化時期; */ AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class); ? /** * 獲取Dao對象,獲取對象時期,并進行query打印 */ Dao dao = annotationConfigApplicationContext.getBean(Dao.class); dao.query(); annotationConfigApplicationContext.start(); } }
好了,這樣我們整體的AOP代理就已經完成。
問題分析測試究竟是哪個時期進行對象織入的,比如Test類中,究竟是第一行還是第二行進行織入的,我們只能通過源碼進行分析,假如是你,你會進行如何的分析源碼解讀。
Spring的代碼非常優秀,同時也非常復雜,那是一個大項目,里面進行了很多的代碼封裝,那么的代碼你三天三夜也讀不完,甚至于你都不清楚哪一行的該留意的,哪一行是起到關鍵性作用的,這里教幾個小技巧。
看方法返回類型;假如是void返回類型的,看都不看跳過。返回結果是對象,比如T果斷進行去進行跟蹤。
假設法;就當前場景,我們大膽假設是第二行進行的織入。
借助好的IDE;IDEA可以幫我們做很多的事情,它的debug模式中的條件斷點、調用鏈(堆棧)會幫助到我們。
假設法源碼分析debug模式StepInfo(F5)后,進入 AbstractApplicationContext.getBean方法,這個是Spring應用上下文中最重要的一個類,這個抽象類中提供了幾乎ApplicationContext的所有操作。這里第一個語句返回void,我們可以直接忽略,看下面的關鍵性代碼。
繼續debug后,會進入到 DefaultListableBeanFactory 類中,看如下代碼
return new NamedBeanHolder<>(beanName, getBean (beanName, requiredType, args));
在該語句中,這個可以理解為 DefaultListableBeanFactory 容器,幫我們獲取相應的Bean。
進入到AbstractBeanFactory類的doGetBean方法之后,我們運行完。
Object sharedInstance = getSingleton(beanName);
語句之后,看到 sharedInstance 對象打印出&Proxyxxx ,說明在getSingleton 方法的時候就已經獲取到了對象,所以需要跟蹤進入到 getSingleton 方法中,繼續探究。
不方便不方便我們進行問題追蹤到這個步驟之后,我需要引入IDEA的條件斷點,不方便我們進行問題追蹤因為Spring會初始化很多的Bean,我們再ObjectsharedInstance=getSingleton(beanName);加入條件斷點語句。
繼續debug進入到DefaultSingletonBeanRegistry的getSingleton方法。
我們觀察下執行完ObjectsingletonObject=this.singletonObjects.get(beanName); 之后的singletonObject已經變成為&ProxyUserDao,這個時候Spring最關鍵的一行代碼出現了,請注意這個this.singletonObjects。
this.singletonObjects就是相當IOC容器,反之IOC容器就是一個線程安全的線程安全的HashMap,里面存放著我們需要Bean。
我們來看下singletonObjects存放著的數據,里面就有我們的UserDao類。
這就說明,我們的初始化的時期進行織入的,上圖也有整個Debug模式的調用鏈。
源碼深層次探索通過上一個環節已經得知是在第一行進行初始化的,但是它在初始化的時候是什么時候完成織入的,抱著求知的心態我們繼續求證。
還是那個問題,那么多的代碼,我的切入點在哪里?
既然singletonObjects是容器,存放我們的Bean,那么找到關鍵性代碼在哪里進行存放(put方法)就可以了。于是我們通過搜索定位到了。
我們通過debug模式的條件斷點和debug調用鏈模式,就可以進行探索。
這個時候借助上圖中的調用鏈,我們把思路放到放到IDEA幫我定位到的兩個方法代碼上。
DefaultSingletonBeanRegistry.getSingleton
我們一步步斷點,得知,當運行完singletonObject=singletonFactory.getObject();之后,singletonObject已經獲得了代理。
至此我們知道,代理對象的獲取關鍵在于singletonFactory對象,于是又定位到了AbstractBeanFactorydoGetBean方法,發現singletonFactory參數是由createBean方法創造的。這個就是Spring中IOC容器最核心的地方了,這個代碼的模式也值得我們去學習。
sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } });
這個第二個參數是用到了jdk8中的lambda,這一段的含義是就是為了傳參,重點看下 createBean(beanName,mbd,args);代碼。隨著斷點,我們進入到這個類方法里面。
AbstractAutowireCapableBeanFactory.createBean中的;
ObjectbeanInstance=doCreateBean(beanName,mbdToUse,args)方法;
doCreateBean方法中,做了簡化。
Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } ... ? return exposedObject;
當運行完 exposedObject=initializeBean(beanName,exposedObject,mbd);之后,我們看到exposedObject已經是一個代理對象,并執行返回。這一行代碼就是取判斷對象要不要執行代理,要的話就去初始化代理對象,不需要直接返回。后面的initializeBean方法是涉及代理對象生成的邏輯(JDK、Cglib),后續會有一個專門的章節進行詳細介紹。
總結通過源碼分析,我們得知,Spring AOP的代理對象的織入時期是在運行Spring初始化的時候就已經完成的織入,并且也分析了Spring是如何完成的織入。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73659.html
摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時間閱讀了方面的源碼。從今天開始,我將對部分的源碼分析系列文章進行更新。全稱是,即面向切面的編程,是一種開發理念。在中,切面只是一個概念,并沒有一個具體的接口或類與此對應。 1. 簡介 前一段時間,我學習了 Spring IOC 容器方面的源碼,并寫了數篇文章對此進行講解。在寫完 Spring IOC 容器源碼分析系列...
摘要:一以及術語是的簡稱,被譯為面向切面編程。切面由切點和增強組成,他包括了連接點定義和橫切邏輯代碼的定義,就是負責實施切面的框架。五使用來定義純粹的切面使用方法也非常簡單,使用的標簽。采用動態代理和動態代理技術在運行期間織入。 引言 AOP是軟件開發思想發展到一定階段的產物,AOP的出現并不是為了代替OOP,僅作為OOP的有益補充,在下面的例子中這個概念將會得到印證。AOP的應用場合是受限...
摘要:關于依賴注入注入的注解提供的注解不僅僅是對象,還有在構造器上,還能用在屬性的方法上。與之相反,的限定符能夠在所有可選的上進行縮小范圍的操作,最終能夠達到只有一個滿足所規定的限制條件。注解是使用限定符的主要方式。 本文首發于泊浮目的專欄:https://segmentfault.com/blog... Spring致力于提供一種方法管理你的業務對象。在大量Java EE的應用中,隨處可...
摘要:如果依賴靠構造器方式注入,則無法處理,直接會報循環依賴異常。光繼承這個接口還不夠,繼承這個接口只能獲取,要想讓生效,還需要拿到切面對象包含和才行。有了目標對象,所有的切面類,此時就可以為生成代理對象了。 Spring 是一個輕量級的 J2EE 開源框架,其目標是降低企業級應用開發難度,提高企業級應用開發效率。在日程開發中,我們會經常使用 Spring 框架去構建應用。所以作為一個經常使...
摘要:值得一提的是由于采用動態創建子類的方式生成代理對象,所以不能對目標類中的方法進行代理。動態代理中生成的代理類是子類,調試的時候可以看到,打開源碼可看到實現了和也就實現方法。 前面講到了動態代理的底層原理,接下來我們來看一下aop的動態代理.Spring AOP使用了兩種代理機制:一種是基于JDK的動態代理,一種是基于CGLib的動態代理. ①JDK動態代理:使用JDK創建代理有一個限制...
閱讀 3189·2021-11-24 10:30
閱讀 1313·2021-09-30 09:56
閱讀 2385·2021-09-07 10:20
閱讀 2596·2021-08-27 13:10
閱讀 698·2019-08-30 11:11
閱讀 2050·2019-08-29 12:13
閱讀 758·2019-08-26 12:24
閱讀 2897·2019-08-26 12:20