曹工說Spring Boot源碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了


寫在前面的話

相關背景及資源:

曹工說Spring Boot源碼(1)-- Bean Definition到底是什么,附spring思維導圖分享

曹工說Spring Boot源碼(2)-- Bean Definition到底是什么,咱們對着接口,逐個方法講解

曹工說Spring Boot源碼(3)-- 手動注冊Bean Definition不比游戲好玩嗎,我們來試一下

曹工說Spring Boot源碼(4)-- 我是怎么自定義ApplicationContext,從json文件讀取bean definition的?

曹工說Spring Boot源碼(5)-- 怎么從properties文件讀取bean

曹工說Spring Boot源碼(6)-- Spring怎么從xml文件里解析bean的

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中得到了什么(上)

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中得到了什么(util命名空間)

曹工說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();
    }

輸出如下:

本講,我們來講講,背后的故事。

ProxyFactory如何構造

其一共有如下幾個重載的構造函數:

  1. 無參構造函數
  2. ProxyFactory(Object target) 指定target
  3. ProxyFactory(Class[] proxyInterfaces) 指定代理類要實現的接口
  4. ProxyFactory(Class proxyInterface, Interceptor interceptor) 指定代理類要實現的接口,以及一個切面
  5. ProxyFactory(Class proxyInterface, TargetSource targetSource) 指定代理類要實現的接口,以及一個targetSource,targetSource類似於一個targetFactory,通過其,間接獲取target

因為spring aop源碼里,默認就使用了的是無參構造函數,這里我們也以無參構造函數來講解。

我們知道,構造函數調用時,如果這個類有父類,還得先調用父類的構造函數。恰巧,這個類就有父類:

其中,ProxyConfig沒有顯示定義的構造函數,所以只有默認的無參構造函數。所以,

  1. 會先調用ProxyConfig的無參構造函數;

  2. 調用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);
    }
    
  3. 接下來,調用ProxyCreatorSupport的無參構造函數:

    /**
     * Create a new ProxyCreatorSupport instance.
     */
    public ProxyCreatorSupport() {
       this.aopProxyFactory = new DefaultAopProxyFactory();
    }
    
  4. 調用ProxyFactory的無參構造函數

    /**
     * Create a new ProxyFactory.
     */
    public ProxyFactory() {
    }
    

其中,比較有的講的,主要是第三個步驟,即ProxyCreatorSupport的無參構造函數。

這一步呢,new了一個DefaultAopProxyFactory,不過,暫時還沒用到它。

構造完了,接下來,就是各種配置上場的時候了。

配置ProxyFactory

好歹這也是一響當當的工廠,但是吧,要生產啥呢?總得有個方向吧。你是一個ProxyFactory,代理工廠,你要代理誰?代理賣火車票,還是代理賣房呢?注意,這里我說的是賣火車票,和賣房。

這說明啥,說明我屁股是坐在賣方的,是12306一方,是要賣房的一方。因為啥呢,因為我現在的target,是賣方,我是作為賣方的代表(即,代理)來出現的。

target很重要,這個直接決定了我們工廠的方向。比如,假設翻轉一下,代理買方。比如,現在中國人,有錢人很多,很多人就去國外買房,比如澳洲、日本、東南亞啥的,但是呢,你對當地不了解,所以,就催生了當地的一批華人,來幫助大陸中國人在那邊買房,此時,他們就是我們的代理。我們呢,就是他們的target。

ok,大家想必理解了,ProxyFactory要生產啥,主要還是要有個定位,看看屁股坐哪邊。所以,我們作為代碼世界的王者,就要負責來定方向。

配置target

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唱歌完了之后,輸出一句話:要行禮。

額外的一些配置

當然了,作為一個齊備的工廠,還是要支持一些客戶的定制功能的。比如:

  1. 從ProxyConfig繼承來的一些方法

    比如,有的客戶說,我要cglib創建代理,有的說,我要jdk。ok,這個就滿足你了。

    再比如,isExposeProxy,這個可以把生成的代理通過一個api提供給你,你可以在target方法內,拿到代理對象。

  2. 從AdvisedSupport繼承來的功能

    這個也簡單,基本就是我們前面那幾個配置的重載方法,增刪改查嘛。

  3. 從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,它怎么創建代理呢?

JdkDynamicAopProxy揭秘

這個類,我直接給大家說,其實現了兩個接口:

  1. 代理接口: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();
    }
    

    這個接口就是獲取代理對象。

  2. 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);
}

這里就簡單的幾步:

  1. 獲取要代理的全部接口

    Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    

    實際上,大家記得,我們前面只配了一個要代理的接口,但這個方法內部,還會給我們加上兩個接口。

    • org.springframework.aop.SpringProxy

      這個是marker接口,空的,不用管,只是做個標記,框架會用到

    • org.springframework.aop.framework.Advised

      這個接口,功能比較全,還是一些增刪改查的操作,對象吧,是那些切面、target啥的,這可以讓我們動態地修改生成的代理對象。

  2. 調用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();
}

上面的代碼,我標了號。

  1. 第一處,是獲取target,根據之前配置的targetSource來獲取,忘了的可以再翻一下;

  2. 第二處,根據當前要執行的method和class,判斷哪些切面(其實就是代理要幫我們做的事)是匹配的

  3. 第三處,如果切面集合為null,說明代理啥事不干,所以只能直接調用target了

    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);

  4. 第四處,如果切面不為null,說明代理有事要做,這里就封裝了一個invocation,來調用切面集合。

這里面有兩點要講,第二處和第四處。

  1. 第二處,獲取匹配的切面時,核心邏輯是,把切面里的切點,和目標類、目標方法一一匹配,都匹配,就算;否則不算。

    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;
    	}
    

    這里就三個講解點,

    • 1是遍歷全部的切面
    • 2是拿出切面中的切點的classMatcher,看看目標class是否匹配
    • 3是拿出切面中的切點的methodMatcher,看看目標方法是否匹配
  2. 實際調用切面和目標方法

    這里用到了責任鏈模式,遞歸執行;其實也可以直接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);
       }
    }
    
    • 講解點1:一開始進來的時候,之前說的那個下標為-1;判斷是否已經是切面集合的最后一個,我們這里剛開始,所以會走到下面
    • 講解點2:一般都不會走到這個分支里,會直接跳到講解點3. 因為我們spring aop,一般都是基於方法的切面嘛。
    • 講解點3:這里,調用切面的invoke方法,傳進去了this。this是啥?就是ReflectiveMethodInvocation自己。

    所以,大家看上圖就知道了,這里形成了遞歸調用。

    我思考了一下,之所以遞歸調用,而不是for循環,主要是要保證target的method先執行,執行完了,才能到我們這里的切面來執行。

    這樣邏輯才對。

    當這里遞歸調用進去時,因為我們只有一個切面,所以就開始執行連接點:

​ 待到連接點執行完了后,會繼續執行我們切面的后續邏輯。

​ 這就是和tomcat filter鏈類似的責任鏈模式。

總結

aop這塊,基本的東西是差不多了,大家有問題及時聯系我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM