問題:Spring AOP代理中的運行時期,是在初始化時期織入仍是獲取對象時期織入?java
織入就是代理的過程,指目標對象進行封裝轉換成代理,實現了代理,就能夠運用各類代理的場景模式。spring
簡單點來定義就是切面,是一種編程範式。與OOP對比,它是面向切面,爲什麼須要切面,在開發中,咱們的系統從上到下定義的模塊中的過程當中會產生一些橫切性的問題,這些橫切性的問題和咱們的主業務邏輯關係不大,假如不進行AOP,會散落在代碼的各個地方,形成難以維護。AOP的編程思想就是把業務邏輯和橫切的問題進行分離,從而達到解耦的目的,使代碼的重用性、侵入性低、開發效率高。sql
在這裏問題中,也有一個相似的一對IOC和DI(dependency injection)的關係,AOP能夠理解是一種編程目標,Spring AOP就是這個實現這個目標的一種手段。同理IOC也是一種編程目標,DI就是它的一個手段。數據庫
在Spring官網能夠看到,AOP的實現提供了兩種支持分別爲@AspectJ、Schema-based AOP。其實在Spring2.5版本時,Spring本身實現了一套AOP開發的規範和語言,可是這一套規範比較複雜,可讀性差。以後,Spring借用了AspectJ編程風格,纔有了@AspectJ的方式支持,那麼何爲編程風格。編程
SpringAOP和AspectJ的詳細對比,在以後的章節會在進行更加詳細的說明,將會在他們的背景、織入方法、性能作介紹。安全
閱讀官網,是咱們學習一個新知識的最好途徑,這個就是Spring AOP的核心概念點,跟進它們的重要性,我作了從新的排序,以便好理解,這些會爲咱們後續的源碼分析起到做用。bash
Aspect:切面;使用@Aspect註解的Java類來實現,集合了全部的切點,作爲切點的一個載體,作一個比喻就像是咱們的一個數據庫。 Tips:這個要實現的話,必定要交給Spirng IOC去管理,也就是須要加入@Component。app
Pointcut:切點;表示爲全部Join point的集合,就像是數據庫中一個表。源碼分析
Join point:鏈接點;俗稱爲目標對象,具體來講就是servlet中的method,就像是數據庫表中的記錄。性能
Advice:通知;這個就是before、after、After throwing、After (finally)。
Weaving:把代理邏輯加入到目標對象上的過程叫作織入。
target:目標對象、原始對象。
aop Proxy:代理對象 包含了原始對象的代碼和增長後的代碼的那個對象。
Tips 這個應用點,有不少的知識點可讓咱們去挖掘,好比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的代碼很是優秀,同時也很是複雜,那是一個大項目,裏面進行了不少的代碼封裝,那麼的代碼你三天三夜也讀不完,甚至於你都不清楚哪一行的該留意的,哪一行是起到關鍵性做用的,這裏教幾個小技巧。
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是如何完成的織入。