6、spring核心源碼解析之aop概況流程1

前情

​ 前面寫了五篇又過於spring ioc與DI的文章,並且都通過源碼進行了一些分析。從前文中,我們可以看到,spring內部解析@Configuration、@Autowired等註解也是通過Bean的後置處理器進行解析的。由此可見spring的後置處理器是很重要的一個擴展點之一。

​ 基於此,我們可以大膽的猜測,spring內部擴展一個新模塊,增加新的註解時,肯定是有相對的後置處理器進行註解解析。同理,以後如果我們想通過結合spring,擴展框架,增加一些新的註解時,也可以設計自己的BeanPostProcess來完成註解解析,bean的注入等功能。

​ 本文主要講解的是spring aop 與 aspects,引用官網的一段話。

​ Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

​ One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP (meaning you do not need to use AOP if you don’t want to), AOP complements Spring IoC to provide a very capable middleware solution.

​ 翻譯:面向切面的編程(AOP)通過提供另一種思考程序結構的方式來補充面向對象的編程(OOP)。 OOP中模塊化的關鍵單元是類,而在AOP中模塊化是切面。 切面使關注點(例如事務管理)的模塊化可以跨越多種類型和對象。 (這種關注在AOP文獻中通常被稱爲“跨領域”關注。)

​ Spring的關鍵組件之一是AOP框架。 儘管Spring IoC容器不依賴於AOP(這意味着您不需要的話就不需要使用AOP),但AOP是對Spring IoC的補充,以提供功能非常強大的中間件解決方案。

1.spring aop和aspects的基本概念

讓我們首先了解一些主要的AOP概念和術語。這些術語不是特定於Spring的。不幸的是,AOP術語並不是特別直觀。但是,如果使用Spring自己的術語,將會更加令人困惑。(想要知道更多可以查看官網,以下概念均來自spring官網

切面( Aspect ):涉及多個類別的關注點的模塊化。事務管理是企業Java應用程序中橫切關注的一個很好的例子。在Spring AOP中,切面是通過使用常規類(基於模式的方法)或使用@Aspect註釋(@AspectJ樣式)註釋的常規類來實現的。

連接點( Join point ):在程序執行過程中的一點,例如方法的執行或異常的處理。在Spring AOP中,連接點始終代表方法的執行。

通知( Advice ):切面在特定的連接點處採取的操作。不同類型的通知包括“周圍”,“之前”和“之後”通知。 (通知類型將在後面討論。)包括Spring在內的許多AOP框架都將通知建模爲攔截器,並在連接點周圍維護一系列攔截器。

切入點( Pointcut ):與連接點匹配的謂詞。通知與切入點表達式關聯,並在與該切入點匹配的任何連接點處運行(例如,執行具有特定名稱的方法)。切入點表達式匹配的連接點的概念是AOP的核心,默認情況下,Spring使用AspectJ切入點表達語言。

簡介( Introduction ):代表類型聲明其他方法或字段。 Spring AOP允許您向任何通知對象引入新的接口(和相應的實現)。例如,您可以使用簡介使Bean實現IsModified接口,以簡化緩存。 (在AspectJ社區中,介紹被稱爲類型間聲明。)

目標對象( Target object ):一個或多個切面通知的對象。也稱爲“通知對象”。由於Spring AOP是使用運行時代理實現的,因此該對象始終是代理對象。

AOP代理( AOP proxy ):由AOP框架創建的一個對象,用於實現切面合同(通知方法執行等)。在Spring Framework中,AOP代理是JDK動態代理或CGLIB代理。

編織( Weaving ):將切面與其他應用程序類型或對象鏈接以創建通知的對象。這可以在編譯時(例如,使用AspectJ編譯器),加載時或在運行時完成。像其他純Java AOP框架一樣,Spring AOP在運行時執行編織。

Spring AOP包括以下類型的通知:

在通知之前( Before advice ):在連接點之前運行的通知,但是它不能阻止執行流程繼續進行到連接點(除非它引發異常)。

返回通知後( After returning ):在連接點正常完成後要運行的通知(例如,如果方法返回而沒有引發異常)。

拋出通知後( After throwing advice ):如果方法因拋出異常而退出,則執行通知。

通知之後(最終)( After (finally) advice ):無論連接點退出的方式如何(正常或特殊返回),均應執行通知。

環繞通知( Around advice ):圍繞連接點的通知,例如方法調用。這是最有力的通知。周圍通知可以在方法調用之前和之後執行自定義行爲。它還負責選擇是返回連接點還是通過返回其自身的返回值或引發異常來捷徑通知的方法執行。

環繞通知是最通用的通知。由於Spring AOP與AspectJ一樣,提供了各種通知類型,因此我們通知您使用功能最弱的通知類型,以實現所需的行爲。例如,如果您只需要使用方法的返回值更新緩存,則最好使用返回後的通知而不是周圍的通知,儘管周圍的通知可以完成相同的事情。使用最具體的通知類型可提供更簡單的編程模型,並減少出錯的可能性。例如,您不需要在用於周圍通知的JoinPoint上調用proce()方法,因此,您不會失敗。

所有通知參數都是靜態類型的,因此您可以使用適當類型(例如,從方法執行返回的值的類型)而不是對象數組的通知參數。

切入點匹配的連接點的概念是AOP的關鍵,它與僅提供攔截功能的舊技術有所不同。切入點使通知的目標獨立於面向對象的層次結構。例如,您可以將提供聲明性事務管理的環繞通知應用於跨越多個對象(例如服務層中的所有業務操作)的一組方法。

​ 講了這麼多的概念,可以自己簡單的通過畫一張圖來加深自己對於aop的理解,當然也可以參考我畫的如下圖所示。

在這裏插入圖片描述

講了這麼多概念,我們可以講一個小案例,不涉及代理,瞭解aop的思想。

在我們剛開始學習jdbc時,肯定都做過以下幾個步驟。但是在實際開發中,基本上看不到這種做法了,因爲爲了簡化開發,我們把重複性代碼,都交給了框架進行處理,我們只用關心核心業務即可。

此時spring jdbc框架做的就是利用aop的設計思想,將核心業務當成一個節點,在覈心業務前後織入我們數據庫的連接等操作,協助完成我們的jdbc操作。

在這裏插入圖片描述

2.aop設計一個統計方法的執行時間註解

採用aop的思想做一個簡單的案例,日常業務開發中也用的着。通常我們要知道一個接口的調用時間時,只需要在接口方法上加一個註解,即可測試出該接口的調用時常。

2.1開啓spring aop功能@EnableAspectJAutoProxy

spring aop功能的開啓很簡單,只需要在依賴中加入aspectj的相關依賴,同時在啓動類上加@EnableAspectJAutoProxy註解即可。

//因爲我是spring源碼,採用的是gradle
dependencies {
    compile(project(":spring-core"))
    compile(project(":spring-context"))
    compile(project(":spring-aspects"))
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
@Configuration
@EnableAspectJAutoProxy //開啓動態代理
public class MyApp {}

2.2編寫切面

@Aspect
public class HandlingTimeAspect {
	//切點,切的是一個註解@HandlingTime
    @Pointcut("@annotation(com.upanda.app.test.annotaition.HandlingTime)")
    public void handlingTimePointcut() {}
	
    //環繞通知
    @Around("handlingTimePointcut()")
    public Object handlingTimeAround(ProceedingJoinPoint joinPoint){
        try {
            long startTime = System.currentTimeMillis();
			System.out.println("方法簽名:"+joinPoint.getSignature());
			System.out.println("方法執行開始時間:{}"+startTime);
            Object proceed = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
			System.out.println("方法執行結束時間:"+endTime);
			System.out.println("方法耗時:"+(endTime-startTime));
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

}

註解@HandlingTime類

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlingTime {
}

2.3通過@EnbleLogger註解導入切面

之後以要通過註解導入切面,是因爲我們可以在啓動類上,通過是否加入了@EnbleLogger註解來實現該功能的可插拔。

@EnbleLogger註解類

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HandlingTimeAspect.class)
public @interface EnableLogger {
}
  1. 使用及測試結果

    在指定方法上加@HandlingTime

    	@HandlingTime
    	public String getName() {
    		return name;
    	}
    

    測試結果

    > Task :spring-upanda:MyApplication.main()
    com.upanda.app.test.Person@5ab9e72c
    方法簽名:String com.upanda.app.test.Person.getName()
    方法執行開始時間:1589704484958
    方法執行結束時間:1589704484970
    方法耗時:12
    coyhzx
    

3.spring之aop運行的整體流程分析

通過上文,我們可以大體的瞭解到,spring aop的一些概念,以及使用流程。

首先開啓aop,使用@EnableAspectJAutoProxy即可。那麼我們可以看看這個註解做了什麼事情

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	//表示是否使用基於子類繼承的CGLIB動態代理,默認是採用基於接口的jdk動態代理,
	boolean proxyTargetClass() default false;

	/**
	 * @since 4.3.1
	 */
	//表示代理應由AOP框架公開爲一個線程變量{@code ThreadLocal} ,
	// 以便通過{@link org.springframework.aop.framework.AopContext}類進行檢索。
	// *默認情況下爲關閉,即不能保證{@code AopContext}訪問將起作用。
	boolean exposeProxy() default false;

}

該註解導入了一個類AspectJAutoProxyRegistrar,該類實現了ImportBeanDefinitionRegistrar接口,該接口是用來向容器中注入bean的(具體用法可參考我的spring源碼分析的第四篇文章4、spring核心源碼解析之自定義bean三種方式@import、ImportSelector、ImportBeanDefinitionRegistrar)。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

該類調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法向我們容器中注入了一個aspectj組件自動代理創建器。

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}

AnnotationAwareAspectJAutoProxyCreator這個類是一個很複雜的類,可以先看一下它的類結構繼承關係圖。
在這裏插入圖片描述

從繼承關係中,我們可以看到兩個spring裏經常使用的兩個擴展點Aware接口,BeanPostProcessor接口。

BeanFactoryAware:實現該接口,可以將BeanFactory注入到我們的AbstractAutoProxyCreator抽象類中。

SmartInstantiationAwareBeanPostProcessor: 實現該接口,我們可以在bean的註冊過程中,織入我們自己的業務邏輯。

基於前幾篇有關於spring ioc文章的源碼分享,其實我們可以大膽的猜測一下,spring是如何完成aop的功能的:

我猜想:spring aop動態代理實現的關鍵邏輯就在於AnnotationAwareAspectJAutoProxyCreator是如何實現了SmartInstantiationAwareBeanPostProcessor、InstantiationAwareBeanPostProcessor、BeanPostProcessor接口中的方法,搞清楚了這些,我覺得spring aop的運作流程就瞭然於心了。

這裏面有很多的父子類繼承關係,以及複雜邏輯,如果把自己看暈了就難了。但是其實長時間看spring代碼之後,我們可以總結出一些小規律(即設計思想,後續可能分析如何去看spring源碼)。

上一篇:5、spring核心源碼解析之DI依賴注入、自動裝配@Autowired和@Resource
[下一篇:7.未完待續]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章