主頁 > 後端開發 > 死磕Spring之AOP篇 - Spring AOP兩種代理物件的攔截處理

死磕Spring之AOP篇 - Spring AOP兩種代理物件的攔截處理

2021-04-23 06:19:40 後端開發

該系列文章是本人在學習 Spring 的程序中總結下來的,里面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼注釋 Spring 原始碼分析 GitHub 地址 進行閱讀,

Spring 版本:5.1.14.RELEASE

在開始閱讀 Spring AOP 原始碼之前,需要對 Spring IoC 有一定的了解,可查看我的 《死磕Spring之IoC篇 - 文章導讀》 這一系列文章

了解 AOP 相關術語,可先查看 《Spring AOP 常見面試題) 》 這篇文章

該系列其他文章請查看:《死磕 Spring 之 AOP 篇 - 文章導讀》

在前面幾篇文章依次介紹了 Spring AOP 自動代理的整個程序,入口在 AbstractAutoProxyCreator 這個類中,它實作了幾種 BeanPostProcessor介面,結合 Spring IoC,在 Bean 的加載程序中支持創建代理物件,通常在 Bean 的初始化后,也就是 Bean 處于一個“成熟態”的時候進行 AOP 代理,整個的處理程序比較復雜,需要找到當前 Spring 背景關系所有的 Advisor,也就是 Advice 的容器介面,通常都是 PointcutAdvisor,還包含了一個 Pointcut 切點,接著就是從這些 Advisor 中篩選出能夠應用于這個 Bean 的 Advisor 出來,經過一些處理程序,最后通過 JdkDynamicAopProxy(JDK 動態代理)或者 ObjenesisCglibAopProxy(CGLIB 動態代理)創建一個代理物件,

本文將會分析 Spring AOP 創建的兩種代理物件的攔截處理是如何進行的,開始之前,我們得知道JDK 動態代理創建的代理物件,攔截處理在 InvocationHandler 實作類中;CGLIB 動態代理創建的代理物件,攔截處理在傳入的 Callback 回呼中,對于這兩種代理物件不是很熟悉的小伙伴可查看我前面的文章??

JDK 動態代理

我們先來簡單回顧一下 Spring AOP 中 JDK 動態代理創建代理物件的程序,如下:

// JdkDynamicAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    // <1> 獲取需要代理的介面(目標類實作的介面,會加上 Spring 內部的幾個介面,例如 SpringProxy)
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // <2> 判斷目標類是否重寫了 `equals` 或者 `hashCode` 方法
    // 沒有重寫在攔截到這兩個方法的時候,會呼叫當前類的實作
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // <3> 呼叫 JDK 的 Proxy#newProxyInstance(..) 方法創建代理物件
    // 傳入的引數就是當前 ClassLoader 類加載器、需要代理的介面、InvocationHandler 實作類
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

可以看到呼叫 Proxy#newProxyInstance(..) 方法的 InvocationHandler 入參就是 this,也就是當前物件,我在前面的文章也講到過,JDK 動態代理創建的代理物件實作了入參中的介面,且繼承 Proxy,代理物件的攔截處理通過 Proxy 父類中 InvocationHandler 來完成,也就是入參中的 InvocationHandler 物件,那么我們一起來看看 JdkDynamicAopProxy(實作了 InvocationHandler)是如何攔截處理的,

JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy,JDK 動態代理類,實作了 InvocationHandler 介面,可創建代理物件

invoke 方法

invoke(Object proxy, Method method, Object[] args) 方法,JDK 動態代理創建的代理物件的攔截處理,如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
	/** 代理物件的配置資訊,例如保存了 TargetSource 目標類來源、能夠應用于目標類的所有 Advisor */
	private final AdvisedSupport advised;

	/** 目標物件是否重寫了 equals 方法 */
	private boolean equalsDefined;

	/** 目標物件是否重寫了 hashCode 方法 */
	private boolean hashCodeDefined;

	/**  代理物件的攔截處理 */
	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		// <1> 獲取目標類的 TargetSource 物件,用于獲取目標類
		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			// <2> 如果攔截到下面幾種情況的方法,則需要進行額外處理

			// <2.1> 如果攔截到的方法是 `equals`,且目標類沒有重寫,則呼叫當前類重寫的 `equals` 方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			// <2.2> 否則,如果攔截到的方法是 `hashCode`,且目標類沒有重寫,則呼叫當前類重寫的 `hashCode` 方法
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			// <2.3> 否則,如果攔截到的是 DecoratingProxy 中的方法,則通過 AopProxyUtils 工具類計算出目標類的 Class 物件
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			// <2.4> 否則,如果攔截到的是 Advised 中的方法,則通過 AopUtils 工具類呼叫該方法(反射)
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			// <3> 如果 `expose-proxy` 屬性為 `true`,則需要暴露當前代理物件
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				// <3.1> 向 AopContext 中設定代理物件,并記錄 ThreadLocal 之前存放的代理物件
				// 這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
				oldProxy = AopContext.setCurrentProxy(proxy);
				// <3.2> 標記這個代理物件被暴露了
				setProxyContext = true;
			}

			// Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			// <4> 獲取目標物件,以及它的 Class 物件
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// Get the interception chain for this method.
			// <5> 獲取能夠應用于該方法的所有攔截器(有序)
			// 不同的 AspectJ 根據 @Order 排序
			// 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
			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.
			// <6> 如果攔截器鏈為空,則直接執行目標方法
			if (chain.isEmpty()) {
				// 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.
				// <6.1> 引數適配處理
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				// <6.2> 執行目標方法(反射),并獲取回傳結果
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			// <7> 否則,需要根據攔截器鏈去執行目標方法
			else {
				// We need to create a method invocation...
				// <7.1> 創建一個方法呼叫器,并將前面第 `5` 步獲取到的攔截器鏈傳入其中
				// 該物件就是 Joinpoint 物件
				MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				// <7.2> 執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),并獲取回傳結果
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			// <8> 獲取目標方法回傳值型別
			Class<?> returnType = method.getReturnType();
			// <9> 如果需要回傳代理物件
			if (retVal != null // 回傳值不為空
					&& retVal == target // 回傳值就是當前目標物件
					&& returnType != Object.class // 回傳值型別不是 Object 型別
					&& returnType.isInstance(proxy)  // 回傳值型別就是代理物件的型別
					&& !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass()))
			{
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				// 將當前代理物件作為回傳結果
				retVal = proxy;
			}
			// <10> 否則,如果回傳值型別為原始型別(基本型別,不能為空)且方法的回傳型別不是 Void,如果回傳值為空則拋出例外
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			// <11> 回傳 `retVal` 回傳結果
			return retVal;
		}
		finally {
			// <12> 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得回傳一個新的目標物件)
			// 那么需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			// <13> 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}
}

整個的攔截處理程序如下:

  1. 獲取目標類的 TargetSource 物件,用于獲取目標類
  2. 如果攔截到下面幾種情況的方法,則需要進行額外處理
    1. 如果攔截到的方法是 equals,且目標類沒有重寫,則呼叫當前類重寫的 equals 方法
    2. 否則,如果攔截到的方法是 hashCode,且目標類沒有重寫,則呼叫當前類重寫的 hashCode 方法
    3. 否則,如果攔截到的是 DecoratingProxy 中的方法,則通過 AopProxyUtils 工具類計算出目標類的 Class 物件
    4. 否則,如果攔截到的是 Advised 中的方法,則通過 AopUtils 工具類呼叫該方法(反射)
  3. 如果 expose-proxy 屬性為 true,則需要暴露當前代理物件
    1. 向 AopContext 中設定代理物件,并記錄 ThreadLocal 之前存放的代理物件,這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
    2. 標記這個代理物件被暴露了
  4. 獲取目標物件,以及它的 Class 物件
  5. 呼叫當前 AdvisedSupport 的 getInterceptorsAndDynamicInterceptionAdvice(..),獲取能夠應用于該方法的所有攔截器(有序)
    • 不同的 AspectJ 根據 @Order 排序
    • 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
  6. 如果攔截器鏈為空,則直接執行目標方法
    1. 引數適配處理
    2. 執行目標方法(反射),并獲取回傳結果
  7. 否則,需要根據攔截器鏈去執行目標方法
    1. 創建一個 ReflectiveMethodInvocation 方法呼叫器,并將前面第 5 步獲取到的攔截器鏈傳入其中
    2. 執行方法呼叫器,會執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),并獲取回傳結果
  8. 獲取目標方法回傳值型別
  9. 如果需要回傳代理物件,將當前代理物件作為回傳結果
  10. 否則,如果回傳值型別為原始型別(基本型別,不能為空)且方法的回傳型別不是 Void,如果回傳值為空則拋出例外
  11. 回傳 retVal 回傳結果
  12. 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得回傳一個新的目標物件),那么需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
  13. 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中

JDK 動態代理創建的代理物件的攔截處理程序整體邏輯上并不復雜,通過上面的描述大致上可以理解,上面程序復雜的是上面的第 5 步和第 7 步,一個是獲取該方法的攔截器們,一個是執行整個攔截器鏈,接下來我們依次分析

至于上面的第 5 步得到的方法攔截器們的順序為什么是這個,可以查看我前面的 《Spring AOP 自動代理(二)篩選合適的通知器》這篇文章

AdvisedSupport

org.springframework.aop.framework.AdvisedSupport,代理物件的配置管理器

獲取能夠應用于方法的攔截器們

getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) 方法,獲取能夠應用于該方法的所有攔截器,如下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    // <1> 創建一個方法快取 Key
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    // <2> 嘗試從快取中獲取
    List<Object> cached = this.methodCache.get(cacheKey);
    // 快取未命中,則進行下一步處理
    if (cached == null) {
        /*
         * <3> 獲取能夠應用于該方法的所有攔截器(有序)
         * 篩選出能夠應用于該方法的所有 Advisor,并獲取對應的 MethodInterceptor,也就是 Advice(如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
         * 因為 Advisor 是排好序的,所以回傳的 MethodInterceptor 也是有序的
         *
         * 為什么 `cached` 使用 `List<Object>` 存盤?
         * 因為有些元素是 MethodInterceptor 和 MethodMatcher 的包裝物件,并不是 MethodInterceptor
         */
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass);
        // <4> 將該方法對應的攔截器鏈路放入快取
        this.methodCache.put(cacheKey, cached);
    }
    // <5> 回傳能夠應用于該方法的所有攔截器(有序)
    return cached;
}

該方法的處理程序如下:

  1. 創建一個方法快取 Key
  2. 嘗試從快取中獲取,快取未命中,則進行下一步處理
  3. 呼叫 DefaultAdvisorChainFactory 的 getInterceptorsAndDynamicInterceptionAdvice(..) 方法,獲取能夠應用于該方法的所有攔截器(有序)
    • 篩選出能夠應用于該方法的所有 Advisor,并獲取對應的 MethodInterceptor,也就是 Advice(如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    • 因為 Advisor 是排好序的,所以回傳的 MethodInterceptor 也是有序的
  4. 將該方法對應的攔截器鏈路放入快取
  5. 回傳能夠應用于該方法的所有攔截器(有序)

這個方法的的處理程序只是嘗試去快取中獲取,快取未命中,則通過 DefaultAdvisorChainFactory 獲取能夠應用于該方法的所有攔截器

DefaultAdvisorChainFactory

org.springframework.aop.framework.DefaultAdvisorChainFactory,獲取攔截器鏈路的默認實作

獲取能夠應用于方法的攔截器們

getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) 方法,獲取能夠應用于該方法的所有攔截器,如下:

@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
        Advised config, Method method, @Nullable Class<?> targetClass) {

    // <1> 獲取 DefaultAdvisorAdapterRegistry 實體物件
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    // <2> 獲取能夠應用到 `targetClass` 的 Advisor 們
    Advisor[] advisors = config.getAdvisors();
    List<Object> interceptorList = new ArrayList<>(advisors.length);
    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
    Boolean hasIntroductions = null;

    // <3> 遍歷上一步獲取到的 Advisor 們
    // 篩選出哪些 Advisor 需要處理當前被攔截的 `method`,并獲取對應的 MethodInterceptor(Advice,如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    for (Advisor advisor : advisors) {
        /*
         * <3.1> 如果是 PointcutAdvisor 型別,則需要對目標物件的型別和被攔截的方法進行匹配
         */
        if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            /*
             * <3.1.1> 判斷這個 PointcutAdvisor 是否匹配目標物件的型別,無法匹配則跳過
             */
            if (config.isPreFiltered() // AdvisedSupport 是否已經過濾過目標物件的型別
                    || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) // 呼叫 Pointcut 的 ClassFilter 對目標物件的型別進行匹配
            {
                // <3.1.2> 獲取 Pointcut 的 MethodMatcher 方法匹配器對該方法進行匹配
                // 參考 AspectJExpressionPointcut,底層借助于 AspectJ 的處理
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                boolean match;
                if (mm instanceof IntroductionAwareMethodMatcher) {
                    if (hasIntroductions == null) {
                        hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
                    }
                    match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
                }
                else {
                    match = mm.matches(method, actualClass);
                }
                /*
                 * <3.1.3> 如果這個方法匹配成功,則進行下面的處理
                 */
                if (match) {
                    // <3.1.4> 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    // <3.1.5> 若 MethodMatcher 的 `isRuntime()` 回傳 `true`,則表明 MethodMatcher 要在運行時做一些檢測
                    if (mm.isRuntime()) {
                        // Creating a new object instance in the getInterceptors() method
                        // isn't a problem as we normally cache created chains.
                        for (MethodInterceptor interceptor : interceptors) {
                            // <3.1.5.1> 將上面獲取到的 MethodInterceptor 和 MethodMatcher 包裝成一個物件,并添加至 `interceptorList`
                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }
                    // <3.1.6> 否則,直接將 MethodInterceptor 們添加至 `interceptorList`
                    else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }
        /*
         * <3.2> 否則,如果是 IntroductionAdvisor 型別,則需要對目標物件的型別進行匹配
         */
        else if (advisor instanceof IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            /*
             * <3.2.1> 判斷這個 IntroductionAdvisor 是否匹配目標物件的型別,無法匹配則跳過
             */
            if (config.isPreFiltered() // AdvisedSupport 是否已經過濾過目標物件的型別
                    || ia.getClassFilter().matches(actualClass)) // 呼叫 Pointcut 的 ClassFilter 對目標物件的型別進行匹配
            {
                // <3.2.2> 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                // <3.2.3> 直接將 MethodInterceptor 們添加至 `interceptorList`
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        /*
         * <3.3> 否則,不需要對目標物件的型別和被攔截的方法進行匹配
         */
        else {
            // <3.3.1> 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            // <3.3.2> 直接將 MethodInterceptor 們添加至 `interceptorList`
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }

    // <4> 回傳 `interceptorList` 所有的 MethodInterceptor 攔截器
    // 因為 Advisor 是排好序的,所以這里的 `interceptorList` 是有序的
    return interceptorList;
}

該方法的處理程序如下:

  1. 獲取 DefaultAdvisorAdapterRegistry 實體物件
  2. 獲取能夠應用到 targetClass 的 Advisor 們
  3. 遍歷上一步獲取到的 Advisor 們,篩選出哪些 Advisor 需要處理當前被攔截的 method,并獲取對應的 MethodInterceptor(Advice,如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    1. 如果是 PointcutAdvisor 型別,則需要對目標物件的型別和被攔截的方法進行匹配
      1. 判斷這個 PointcutAdvisor 是否匹配目標物件的型別(ClassFilter),無法匹配則跳過
      2. 獲取 Pointcut 的 MethodMatcher 方法匹配器對該方法進行匹配,參考 AspectJExpressionPointcut,底層借助于 AspectJ 的處理
      3. 如果這個方法匹配成功,則進行下面的處理
      4. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      5. 若 MethodMatcher 的 isRuntime() 回傳 true,則表明 MethodMatcher 要在運行時做一些檢測
        1. 將上面獲取到的 MethodInterceptor 和 MethodMatcher 包裝成一個物件,并添加至 interceptorList
      6. 否則,直接將 MethodInterceptor 們添加至 interceptorList
    2. 否則,如果是 IntroductionAdvisor 型別,則需要對目標物件的型別進行匹配
      1. 判斷這個 IntroductionAdvisor 是否匹配目標物件的型別(ClassFilter),無法匹配則跳過
      2. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      3. 直接將 MethodInterceptor 們添加至 interceptorList
    3. 否則,不需要對目標物件的型別和被攔截的方法進行匹配,直接通過
      1. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,并包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      2. 直接將 MethodInterceptor 們添加至 interceptorList
  4. 回傳 interceptorList 所有的 MethodInterceptor 攔截器,因為 Advisor 是排好序的,所以這里的 interceptorList 是有序的

這里做一個小結,整個處理程序并不復雜,當時創建代理物件的時候篩選出了能夠應用于當前 Bean 的所有 Advisor,現在要做的是先從這些 Advisor 中篩選出能夠應用于當前方法的 Advisor,然后通過 DefaultAdvisorAdapterRegistry 獲取 Advisor 對應的 MethodInterceptor 方法攔截器,對于 PointcutAdvisorIntroductionAdvisor 處理稍微有點不同,因為前者多了一個 Pointcut,需要通過它的 MethodMatcher 對方法進行匹配,其他的差不多,

DefaultAdvisorAdapterRegistry

org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry,默認的 Advisor 配接器注冊中心,主要是對 Advisor 中的 Advice 進行匹配處理

wrap 方法

@Override
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    if (adviceObject instanceof Advisor) { // Advisor 型別,直接回傳
        return (Advisor) adviceObject;
    }
    if (!(adviceObject instanceof Advice)) { // 非 Advice 介面,拋出例外
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) { // MethodInterceptor 型別,包裝成 DefaultPointcutAdvisor 物件
        // So well-known it doesn't even need an adapter.
        return new DefaultPointcutAdvisor(advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        // Check that it is supported.
        // 檢查該 Advice 型別是否支持
        if (adapter.supportsAdvice(advice)) {
            // 包裝成 DefaultPointcutAdvisor 物件 回傳
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

將 Advice 包裝成 Advisor 物件

getInterceptors 方法

@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<>(3);
    // <1> 獲取 Advice 通知器
    Advice advice = advisor.getAdvice();
    /*
     * <2> 若 Advice 是 MethodInterceptor 型別的,直接添加到 `interceptors`即可
     * 例如 AspectJAfterThrowingAdvice、AspectJAfterAdvice、AspectJAroundAdvice
     */
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }

    /*
     * <3> 通過 Advisor 配接器將 Advice 封裝成對應的 MethodInterceptor 物件,并添加至 `interceptors`
     * AspectJAfterReturningAdvice -> AfterReturningAdviceInterceptor
     * AspectJMethodBeforeAdvice -> MethodBeforeAdviceInterceptor
     */
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    // <4> 沒有對應的 MethodInterceptor 則拋出例外
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    // <5> 將 `interceptors` 轉換成陣列并回傳
    return interceptors.toArray(new MethodInterceptor[0]);
}

獲取 Advisor 中的 MethodInterceptor 方法攔截器:

  1. 獲取 Advice 通知器
  2. 若 Advice 是 MethodInterceptor 型別的,直接添加到 interceptors即可
    • 例如 AspectJAfterThrowingAdvice、AspectJAfterAdvice、AspectJAroundAdvice
  3. 通過 Advisor 配接器將 Advice 封裝成對應的 MethodInterceptor 物件,并添加至 interceptors
    • AspectJAfterReturningAdvice -> AfterReturningAdviceInterceptor
    • AspectJMethodBeforeAdvice -> MethodBeforeAdviceInterceptor
  4. 沒有對應的 MethodInterceptor 則拋出例外
  5. interceptors 轉換成陣列并回傳

可以看到,在 Spring AOP 的攔截處理中,使用的 Advice 都是 MethodInterceptor 方法攔截器

ReflectiveMethodInvocation

org.springframework.aop.framework.ReflectiveMethodInvocation,代理物件目標方法的呼叫器,包含方法對應的 MethodInterceptor 攔截器鏈

建構式

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

	/** 代理物件 */
	protected final Object proxy;
	/** 目標物件 */
	@Nullable
	protected final Object target;
	/** 目標方法 */
	protected final Method method;
	/** 方法入參 */
	protected Object[] arguments;
	/** 目標物件的 Class 物件 */
	@Nullable
	private final Class<?> targetClass;
	/** 自定義屬性 */
	@Nullable
	private Map<String, Object> userAttributes;
	/** 方法的攔截器鏈路 */
	protected final List<?> interceptorsAndDynamicMethodMatchers;
	/** 當前已經執行完的攔截器的位置索引,執行完則執行目標方法 */
	private int currentInterceptorIndex = -1;

	protected ReflectiveMethodInvocation(
			Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
			@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		// 獲取目標方法,如果是橋接方法則會找到目標方法
		this.method = BridgeMethodResolver.findBridgedMethod(method);
		// 對該方法引數進行適配處理(如果有必要)
		this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}
}

上面的屬性通過上面的注釋進行理解即可

invokeJoinpoint 方法

invokeJoinpoint() 方法,執行目標方法,如下:

@Nullable
protected Object invokeJoinpoint() throws Throwable {
    return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
        throws Throwable {
    try {
        ReflectionUtils.makeAccessible(method);
        return method.invoke(target, args);
    } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
    } catch (IllegalArgumentException ex) {
        throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
                method + "] on target [" + target + "]", ex);
    } catch (IllegalAccessException ex) {
        throw new AopInvocationException("Could not access method [" + method + "]", ex);
    }
}

基于反射執行目標方法

proceed 方法

proceed() 方法,方法呼叫器的執行,如下:

@Override
@Nullable
public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    // <1> 如果當前已經執行完的攔截器的位置索引就是最后一個,那么即可執行目標方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 執行目標方法(底層反射機制)
        return invokeJoinpoint();
    }

    // <2> 按順序獲取攔截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    /**
     * <3> 如果是 InterceptorAndDynamicMethodMatcher 型別,表示 MethodMatcher 在真正的執行時需要做一些檢測
     * 參考 {@link DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice }
     */
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        // <3.1> 通過 MethodMatcher 對目標方法進行匹配
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            // 匹配通過,則執行這個攔截器,并傳遞當前物件
            return dm.interceptor.invoke(this);
        }
        // <3.2> 否則,直接跳過這個攔截器
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        // <4> 否則執行這個攔截器,并傳遞當前物件
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

方法呼叫器的執行程序如下:

  1. 如果 currentInterceptorIndex(已經執行完的攔截器的位置索引)就是最后一個,那么即可執行目標方法,呼叫 invokeJoinpoint() 方法
  2. 按順序獲取攔截器,先將 currentInterceptorIndex 自增 1
  3. 如果是 InterceptorAndDynamicMethodMatcher 型別,表示 MethodMatcher 在真正的執行時需要做一些檢測
    1. 通過 MethodMatcher 對目標方法進行匹配,匹配通過,則執行這個攔截器,并傳遞當前物件,呼叫 MethodInterceptor#invoke(this) 方法
    2. 否則,直接跳過這個攔截器,繼續呼叫當前 proceed() 方法
  4. 否則執行這個攔截器,并傳遞當前物件,呼叫 MethodInterceptor#invoke(this) 方法

上面第 3 步,為什么可能存在 InterceptorAndDynamicMethodMatcher 物件,回傳前面的 DefaultAdvisorChainFactory 可以知曉答案

可以看到整個的方法呼叫程序是根據 MethodInterceptor 的順序一個一個往下執行的,執行完了則執行目標方法,

那么你是否有疑問,為什么執行完最后一個攔截器才執行目標方法,后置通知器不是需要等目標方法執行后才進行處理的嗎?其實你進入 Advice 實作的 MethodInterceptor#invoke(MethodInvocation) 方法就知道答案了,可查看下面這張圖,

方法呼叫器的執行攔截器鏈圖

到這里, JDK 動態代理創建的代理物件的攔截處理程序全部分析完了,做一個小的總結:

  1. 在攔截處理某個方法的時候,需要先通過 AdvisedSupport -> DefaultAdvisorChainFactory -> DefaultAdvisorAdapterRegistry 獲取到能夠應用于該方法的 Advisor 們,實體拿到的是它們對應的 MethodInterceptor 攔截器們,他們的順序如上面這張圖所示;

  2. 獲取到了 MethodInterceptor 方法攔截器后,創建一個 ReflectiveMethodInvocation 方法呼叫器,進行方法的攔截處理,依次呼叫每個 MethodInterceptor 的 invoke(..) 方法,在最后執行目標方法;

  3. 在上面這張圖你可以看到不同 MethodInterceptor 的執行順序,不過實際 Advice 的執行邏輯順序是:

    Around 前處理 > Before > Around 后處理 > After > AfterReturning|AfterThrowing

------------------------------------

CGLIB 動態代理

我們先來簡單回顧一下 Spring AOP 中 CGLIB 動態代理創建代理物件的物件:

// CglibAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    // ... 下面只展示部分代碼
    
    // <5> 創建 CGLIB 的增強類,并進行接下來的配置
    Enhancer enhancer = createEnhancer();
    // <5.1> 設定被代理的類
    enhancer.setSuperclass(proxySuperClass);
    // <5.2> 設定需要代理的介面(可能沒有,不過都會加上 Spring 內部的幾個介面,例如 SpringProxy)
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    // <5.3> 設定命名策略,默認生成的代理物件的名稱中包含 '$$' 和 'BySpringCGLIB'
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

    // <5.4> 獲取回呼介面,也就是 MethodInterceptor 方法攔截器
    Callback[] callbacks = getCallbacks(rootClass);
    Class<?>[] types = new Class<?>[callbacks.length];
    for (int x = 0; x < types.length; x++) {
        types[x] = callbacks[x].getClass();
    }
    // <5.5> 設定 Callback 過濾器,用于篩選出方法對應的 Callback 回呼介面
    enhancer.setCallbackFilter(new ProxyCallbackFilter(
            this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
    enhancer.setCallbackTypes(types);

    // Generate the proxy class and create a proxy instance.
    // <6> 創建代理物件,CGLIG 位元組碼替身,創建目標類的子類
    return createProxyClassAndInstance(enhancer, callbacks);
}

可以看到通過 Enhancer 創建代理物件,也就是目標類的子類,設定了一個 Callback 陣列和 CallbackFilter 過濾器,CallbackFilter 用于獲取目標方法對應的 Callback,這些內容都在上一篇《Spring AOP 自動代理(三)創建代理物件》文章中分析過,這里不再講述,上一篇文章知道,CGLIB 進行 AOP 代理的通用攔截器是 DynamicAdvisedInterceptor 物件,那么接下來我們來看看這個攔截是怎么處理的,

DynamicAdvisedInterceptor

org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor,CglibAopProxy 的私有靜態內部類

intercept 方法

intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) 方法,CGLIB 動態代理創建代理物件的通用攔截處理方法,如下:

private final AdvisedSupport advised;

@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    // <1> 獲取目標類的 TargetSource 物件,用于獲取目標類
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        // <2> 如果 `expose-proxy` 屬性為 `true`,則需要暴露當前代理物件
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            // <2.1> 向 AopContext 中設定代理物件,并記錄 ThreadLocal 之前存放的代理物件
            // 這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
            oldProxy = AopContext.setCurrentProxy(proxy);
            // <2.2> 標記這個代理物件被暴露了
            setProxyContext = true;
        }
        // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
        // <3> 獲取目標物件,以及它的 Class 物件
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // <4> 獲取能夠應用于該方法的所有攔截器(有序)
        // 不同的 AspectJ 根據 @Order 排序
        // 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // <5> 如果攔截器鏈為空,則直接執行目標方法
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // <5.1> 引數適配處理
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            // <5.2> 執行目標方法(反射),并獲取回傳結果
            retVal = methodProxy.invoke(target, argsToUse);
        }
        // <6> 否則,需要根據攔截器鏈去執行目標方法
        else {
            // <6.1> 創建一個方法呼叫器,并將前面獲取到的攔截器鏈傳入其中,該物件就是 Joinpoint 物件
            // <6.2> 執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),并獲取回傳結果
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        // <7> 對最終的回傳結果進一步處理(回傳結果是否需要為代理物件,回傳結果是否不能為空)
        retVal = processReturnType(proxy, target, method, retVal);
        // <8> 回傳 `retVal` 回傳結果
        return retVal;
    }
    finally {
        // <9> 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得回傳一個新的目標物件)
        // 那么需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        // <10> 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

整個的攔截處理程序:

  1. 獲取目標類的 TargetSource 物件,用于獲取目標類
  2. 如果 expose-proxy 屬性為 true,則需要暴露當前代理物件
    1. 向 AopContext 中設定代理物件,并記錄 ThreadLocal 之前存放的代理物件
    2. 標記這個代理物件被暴露了
  3. 獲取目標物件,以及它的 Class 物件
  4. 呼叫當前 AdvisedSupport 的 getInterceptorsAndDynamicInterceptionAdvice(..),獲取能夠應用于該方法的所有攔截器(有序)
    • 不同的 AspectJ 根據 @Order 排序
    • 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
  5. 如果攔截器鏈為空,則直接執行目標方法
    1. 引數適配處理
    2. 執行目標方法(反射),并獲取回傳結果
  6. 否則,需要根據攔截器鏈去執行目標方法
    1. 創建一個 CglibMethodInvocation 方法呼叫器,并將前面第 5 步獲取到的攔截器鏈傳入其中
    2. 執行方法呼叫器,會執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),并獲取回傳結果
  7. 對最終的回傳結果進一步處理(回傳結果是否需要為代理物件,回傳結果是否不能為空)
  8. 回傳 retVal 回傳結果
  9. 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得回傳一個新的目標物件),那么需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
  10. 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中

CGLIB 動態代理創建代理物件的通用攔截處理程序,和前面講到的 JDK 動態代理創建的代理物件差不多,這里不再講述,這里不同的是創建的方法呼叫器是 CglibMethodInvocation 物件,我們一起來看看這個物件

CglibMethodInvocation

org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation,CglibAopProxy 的私有靜態內部類

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

    @Nullable
    private final MethodProxy methodProxy;

    public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
            Object[] arguments, @Nullable Class<?> targetClass,
            List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {

        super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);

        // Only use method proxy for public methods not derived from java.lang.Object
        // 設定代理方法物件
        this.methodProxy = (Modifier.isPublic(method.getModifiers()) // 方法被 public 修飾
                && method.getDeclaringClass() != Object.class // 不是 Object 中的方法
                && !AopUtils.isEqualsMethod(method) // 不是 `equals` 方法
                && !AopUtils.isHashCodeMethod(method) // 不是 `hashCode` 方法
                && !AopUtils.isToStringMethod(method) // 不是 `toString` 方法
                ? methodProxy : null);
    }
    
    @Override
    protected Object invokeJoinpoint() throws Throwable {
        if (this.methodProxy != null) {
            // 執行代理方法物件(反射)
            return this.methodProxy.invoke(this.target, this.arguments);
        }
        else {
            // 執行目標方法物件(反射)
            return super.invokeJoinpoint();
        }
    }
}

可以看到它繼承了 ReflectiveMethodInvocation 這個類,重寫了 invokeJoinpoint() 執行目標方法的方法,區別在于 CGLIB 使用 MethodProxy 執行目標方法

總結

Spring AOP 有 JDK 動態代理CGLIB 動態代理 兩種創建代理物件的方式,前者通過 JdkDynamicAopProxy 創建代理物件,對應的 InvocationHandler 就是這個 JdkDynamicAopProxy 物件;后者通過 CglibAopPeoxy 創建代理物件,會設定了一個 Callback 陣列和 CallbackFilter 過濾器,CallbackFilter 用于獲取目標方法對應的 Callback,其中進行 AOP 代理的通用攔截器是 DynamicAdvisedInterceptor 方法攔截器,

  • JdkDynamicAopProxy:先獲取到能應用于方法的所有 MethodInterceptor(也就是 Advice),然后通過 ReflectiveMethodInvocation 方法呼叫器進行處理
  • DynamicAdvisedInterceptor:和上者處理邏輯差不多,區別是通過 CglibMethodInvocation 方法呼叫器進行處理,它重寫了 ReflectiveMethodInvocation 執行目標方法的方法

至于 ReflectiveMethodInvocation 方法呼叫器的執行邏輯可以參考上面的講解

到這里,關于 Spring AOP 自動代理,以及代理物件的攔截處理到這里已經全部講述完了,其中肯定存在不少的問題,如有疑惑,可在留言區進行留言,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/279191.html

標籤:其他

上一篇:Python系列爬蟲之下載B站視頻

下一篇:Python input()函式用法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more