相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?json
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanapi
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的數組
曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)tomcat
曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)app
曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)框架
曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中獲得了什麼(context:annotation-config 解析)
曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(此次來講說它的奇技淫巧)
曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中獲得了什麼(context:component-scan完整解析)
曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)
曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation集成
曹工說Spring Boot源碼(15)-- Spring從xml文件裏到底獲得了什麼(context:load-time-weaver 完整解析)
曹工說Spring Boot源碼(16)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【上】)
曹工說Spring Boot源碼(17)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【中】)
曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)
曹工說Spring Boot源碼(19)-- Spring 帶給咱們的工具利器,建立代理不用愁(ProxyFactory)
曹工說Spring Boot源碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操做日誌
工程結構圖:
前面兩三篇,介紹了spring aop得以實現的利器:ProxyFactory。
ProxyFactory,全稱:org.springframework.aop.framework.ProxyFactory,spring帝國spring aop軍工廠boss,職責就是生產proxy,即,代理工廠。
經過下面幾行代碼,就能生成一個代理對象,並且咱們還加了了一個環繞通知:
@Test public void createJdkDynamicProxyWithAdvisor() { ProxyFactory proxyFactory = new ProxyFactory(); Performer performer = new Performer(); proxyFactory.setTarget(performer); proxyFactory.addInterface(Perform.class); DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object result = invocation.proceed(); System.out.println("男孩唱完要行禮"); return result; } }); proxyFactory.addAdvisor(advisor); Perform proxy = (Perform) proxyFactory.getProxy(); ProxyFactoryTest.log.info("proxy class:{}",proxy.getClass().getName()); proxy.sing(); }
輸出以下:
本講,咱們來說講,背後的故事。
其一共有以下幾個重載的構造函數:
由於spring aop源碼裏,默認就使用了的是無參構造函數,這裏咱們也以無參構造函數來說解。
咱們知道,構造函數調用時,若是這個類有父類,還得先調用父類的構造函數。恰巧,這個類就有父類:
其中,ProxyConfig沒有顯示定義的構造函數,因此只有默認的無參構造函數。因此,
會先調用ProxyConfig的無參構造函數;
調用AdvisedSupport的無參構造函數,以下:
/** * No-arg constructor for use as a JavaBean. */ public AdvisedSupport() { initMethodCache(); } /** * Initialize the method cache. */ private void initMethodCache() { this.methodCache = new ConcurrentHashMap<MethodCacheKey, List<Object>>(32); }
接下來,調用ProxyCreatorSupport的無參構造函數:
/** * Create a new ProxyCreatorSupport instance. */ public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); }
調用ProxyFactory的無參構造函數
/** * Create a new ProxyFactory. */ public ProxyFactory() { }
其中,比較有的講的,主要是第三個步驟,即ProxyCreatorSupport的無參構造函數。
這一步呢,new了一個DefaultAopProxyFactory,不過,暫時還沒用到它。
構造完了,接下來,就是各類配置上場的時候了。
好歹這也是一響噹噹的工廠,可是吧,要生產啥呢?總得有個方向吧。你是一個ProxyFactory,代理工廠,你要代理誰?代理賣火車票,仍是代理賣房呢?注意,這裏我說的是賣火車票,和賣房。
這說明啥,說明我屁股是坐在賣方的,是12306一方,是要賣房的一方。由於啥呢,由於我如今的target,是賣方,我是做爲賣方的表明(即,代理)來出現的。
target很重要,這個直接決定了咱們工廠的方向。好比,假設翻轉一下,代理買方。好比,如今中國人,有錢人不少,不少人就去國外買房,好比澳洲、日本、東南亞啥的,可是呢,你對當地不瞭解,因此,就催生了當地的一批華人,來幫助大陸中國人在那邊買房,此時,他們就是咱們的代理。咱們呢,就是他們的target。
ok,你們想必理解了,ProxyFactory要生產啥,主要仍是要有個定位,看看屁股坐哪邊。因此,咱們做爲代碼世界的王者,就要負責來定方向。
Performer performer = new Performer(); proxyFactory.setTarget(performer);
定了target,基本定了一半了。
固然,你也不能夠不直接定target,定接口也行。
proxyFactory.addInterface(Perform.class);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object result = invocation.proceed(); Method method = invocation.getMethod(); if (method.getName().equals("sing")) { System.out.println("男孩唱完要行禮"); } return result; } }); proxyFactory.addAdvisor(advisor);
這個步驟是可選的,你也能夠沒有切面,沒有的話,默認就是代理啥事都不幫你作,你讓他幫你分析房產,結果人只收錢不幹活。
咱們這裏的切面,是在target唱歌完了以後,輸出一句話:要行禮。
固然了,做爲一個齊備的工廠,仍是要支持一些客戶的定製功能的。好比:
從ProxyConfig繼承來的一些方法
好比,有的客戶說,我要cglib建立代理,有的說,我要jdk。ok,這個就知足你了。
再好比,isExposeProxy,這個能夠把生成的代理經過一個api提供給你,你能夠在target方法內,拿到代理對象。
從AdvisedSupport繼承來的功能
這個也簡單,基本就是咱們前面那幾個配置的重載方法,增刪改查嘛。
從ProxyCreatorSupport繼承來的功能
這個嘛,基本就是擴展了一下,搞了點事件/監聽的機制,方便咱們擴展。
ok,配也配好了,是否是該把代理對象給人家了。
我寫着寫着,發現這個東西,很像開一個煎餅店,好比根據客戶要求:要雞蛋、培根、雞排啥的(這個就是對應上面的配置部分);而後,這一步,咱們做爲店老闆,就開始去根據客戶的要求,煎煎餅!
Perform proxy = (Perform) proxyFactory.getProxy();
煎餅的過程如何,咱們來看看:
public Object getProxy() { return createAopProxy().getProxy(); }
是否是很簡單,其實,咱們應該分爲兩步來看:
public Object getProxy() { /** * AopProxy是一個接口,實現類有jdk動態代理、cglib兩種 */ AopProxy aopProxy = createAopProxy(); return aopProxy.getProxy(); }
這一步,就是對應:
/** * AopProxy是一個接口,實現類有jdk動態代理、cglib兩種 */ AopProxy aopProxy = createAopProxy();
由於咱們這裏,AopProxy有兩種實現,要用哪種,要根據以前的配置來,好比,指定了proxyTargetClass,那就是要用cglib;不然就用jdk 動態代理。
咱們具體看看:
protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } /** * 其實這裏獲取的,就是以前構造函數時那個DefaultAopProxyFactory */ AopProxyFactory aopProxyFactory = getAopProxyFactory(); return aopProxyFactory.createAopProxy(this); }
這裏,先獲取了AopProxyFactory,這裏呢,拿到的,就是以前咱們構造函數時候那個。
/** * Return the AopProxyFactory that this ProxyConfig uses. */ public AopProxyFactory getAopProxyFactory() { return this.aopProxyFactory; }
這裏拿到DefaultAopProxyFactory後,程序會調用其createAopProxy(this),且把當前對象都傳進去了,當前對象是誰?就是ProxyFactory代理工廠本廠。
具體的建立代碼以下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return CglibProxyFactory.createCglibProxy(config); } else { return new JdkDynamicAopProxy(config); } }
注意看最上面的if判斷:
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))
是否是,若是isProxyTargetClass爲true,或者hasNoUserSuppliedProxyInterfaces,按裏面理解,沒有提供接口,則會走下面的邏輯,去用cglib建立代理。
由於咱們這裏是提供了接口的,因此,會new一個:jdk的動態代理。
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { throw new AopConfigException("No advisors and no TargetSource specified"); } this.advised = config; }
這裏能夠看到,構造函數很簡單,就是把代理工廠本廠的引用傳給他了。咱們前面配了那麼多東西在ProxyFactory上,怎麼能說給人就給人?
廢話,不給JdkDynamicAopProxy,它怎麼建立代理呢?
這個類,我直接給你們說,其實現了兩個接口:
代理接口:AopProxy
public interface AopProxy { /** * Create a new proxy object. * <p>Uses the AopProxy's default class loader (if necessary for proxy creation): * usually, the thread context class loader. * @return the new proxy object (never {@code null}) * @see Thread#getContextClassLoader() */ Object getProxy(); }
這個接口就是獲取代理對象。
java.lang.reflect.InvocationHandler接口
這個接口,熟悉jdk動態代理的就知道,攔截的邏輯就寫在這裏面。咱們大概能夠猜想,代理對象調用方法時,就會被攔截到這個方法裏面來處理。
前面,咱們已經講解了這一步了:
立刻就要調用getProxy來生成代理對象。
org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader) 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); }
這裏就簡單的幾步:
獲取要代理的所有接口
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
實際上,你們記得,咱們前面只配了一個要代理的接口,但這個方法內部,還會給咱們加上兩個接口。
org.springframework.aop.SpringProxy
這個是marker接口,空的,不用管,只是作個標記,框架會用到
org.springframework.aop.framework.Advised
這個接口,功能比較全,仍是一些增刪改查的操做,對象吧,是那些切面、target啥的,這可讓咱們動態地修改生成的代理對象。
調用jdk方法,生成代理
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
這裏沒啥說的,惟一就是,第三個參數,傳了個this,這裏的this,就是JdkDynamicAopProxy它本身。前面咱們也說了,它本身實現了java.lang.reflect.InvocationHandler。
咱們再想一想代理的做用,不就是幫咱們乾點事嗎?那要怎麼幫咱們target幹事呢?
注意,當咱們拿到ProxyFactory的getProxy返回的對象時,其類型已經有點奇怪,你看上圖,它的類型是$Proxy5.
這是jdk動態生成的class。
因此,咱們調用,其實是在代理對象上進行調用,對他們進行調用,實際的邏輯會被跳轉到以前生成代理時,傳進去的那個invocationHandler對象的invoke裏面去。
這個頁面,熟悉吧,不用我多說了,但凡你們在service層加了事務,debug時,進去的就是這個地方。
方法的核心邏輯,大概以下:
Object retVal; // 1. May be null. Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } // 2. Get the interception chain for this method. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // 3. We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // 4. We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); }
上面的代碼,我標了號。
第一處,是獲取target,根據以前配置的targetSource來獲取,忘了的能夠再翻一下;
第二處,根據當前要執行的method和class,判斷哪些切面(其實就是代理要幫咱們作的事)是匹配的
第三處,若是切面集合爲null,說明代理啥事不幹,因此只能直接調用target了
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
第四處,若是切面不爲null,說明代理有事要作,這裏就封裝了一個invocation,來調用切面集合。
這裏面有兩點要講,第二處和第四處。
第二處,獲取匹配的切面時,核心邏輯是,把切面裏的切點,和目標類、目標方法一一匹配,都匹配,就算;不然不算。
public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { // This is somewhat tricky... we have to process introductions first, // but we need to preserve order in the ultimate list. List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); // 講解點1 for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; // 講解點2 if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor); MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); // 講解點3 if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) { interceptorList.addAll(Arrays.asList(interceptors)); } } } else { Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; }
這裏就三個講解點,
實際調用切面和目標方法
這裏用到了責任鏈模式,遞歸執行;其實也能夠直接for循環的。
這裏new了一個ReflectiveMethodInvocation,這個其實就是一個wrapper,包裹了全部必要的參數,能夠理解爲大雜燴,主要是封裝一下,代碼不那麼亂。
protected ReflectiveMethodInvocation( Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; this.method = BridgeMethodResolver.findBridgedMethod(method); this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; }
看,是否是,這裏面啥都有了,代理對象、目標對象、目標class,目標方法,方法參數,切面集合。
同時,這裏面還有個隱含的數組下標:
/** * Index from 0 of the current interceptor we're invoking. * -1 until we invoke: then the current interceptor. */ private int currentInterceptorIndex = -1;
這玩意主要是用來遍歷切面集合的。
好了,接下來講下面這處:
else { // 這一步已經講解完了,拿到了ReflectiveMethodInvocation 對象 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); }
接下來,咱們看看invocation.proceed();
public Object proceed() throws Throwable { //講解點1: if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); // 講解點2: if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. return proceed(); } } else { // 講解點3: // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
因此,你們看上圖就知道了,這裏造成了遞歸調用。
我思考了一下,之因此遞歸調用,而不是for循環,主要是要保證target的method先執行,執行完了,才能到咱們這裏的切面來執行。
這樣邏輯纔對。
當這裏遞歸調用進去時,由於咱們只有一個切面,因此就開始執行鏈接點:
待到鏈接點執行完了後,會繼續執行咱們切面的後續邏輯。
這就是和tomcat filter鏈相似的責任鏈模式。
aop這塊,基本的東西是差很少了,你們有問題及時聯繫我。