Spring 源碼學習(八) AOP 使用和實現原理

咱們在業務開發中,使用得最多的是面向對象編程(OOP),由於它的代碼邏輯直觀,從上往下就能查看完整的執行鏈路。php

在這個基礎上延伸,出現了面向切面編程(AOP),將能夠重複性的橫切邏輯抽取到統一的模塊中。html

例如日誌打印、安全監測,若是按照 OOP 的思想,在每一個方法的先後都要加上重複的代碼,以後要修改的話,更改的地方就會太多,致使很差維護。因此出現了 AOP 編程, AOP 所關注的方向是橫向的,不一樣於 OOP縱向java

因此接下來一塊兒來學習 AOP 是如何使用以及 Spring 容器裏面的處理邏輯~git


如何使用

以前因爲業務開發中須要用到 AOP,因此也整理一篇 Spring自定義註解實現AOP,感興趣的同窗能夠去看看~github

接下來是書中的例子:spring

建立用於攔截的 bean

public class TestAopBean {

	private String testStr = "testStr";

	public void testAop() {
	    // 被攔截的方法,簡單打印
		System.out.println("I am the true aop bean");
	}
}
複製代碼

建立 Advisor

@Aspect
public class AspectJTest {

	@Pointcut("execution(* *.testAop(..))")
	public void test() {

	}

	@Before("test()")
	public void beforeTest() {
		System.out.println("before Test");
	}


	@After("test()")
	public void afterTest() {
		System.out.println("after Test");
	}

	@Around("test()")
	public Object aroundTest(ProceedingJoinPoint joinPoint) {
		System.out.println("around Before");
		Object o = null;
		try {
			// 調用切面的方法
			o = joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("around After");
		return o;
	}
}
複製代碼

首先類打上了 @Aspect 註解,讓 Spring 認識到這個是一個切面 bean,在方法打上 @Pointcut("execution(* *.testAop(..))"),表示這是一個切點方法,execution() 內部的表達式指明被攔截的方法,BeforeAfterAround 分別表示在被攔截方法的前、後已經環繞執行。express


建立配置文件 aop.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--開啓 AOP 功能-->
	<aop:aspectj-autoproxy />

	<bean id="aopTestBean" class="aop.TestAopBean"/>

	<bean class="aop.AspectJTest" />
</beans>
複製代碼

測試 Demo

public class AopTestBootstrap {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
		TestAopBean bean = (TestAopBean) context.getBean("aopTestBean");
		bean.testAop();
		// 輸出內容 看輸出順序,瞭解到加強方法的執行順序 :
		// Around proceed 以前 -> Before -> Around proceed 以後 -> After
		//around Before
		//before Test
		//I am the true aop bean
		//around After
		//after Test
	}
}
複製代碼

根據上面的啓動例子,發如今本身寫的核心業務方法 testAop() 上,明明只是簡單打印了 I am the true aop bean,但執行結果輸出了其它內容,說明這個類被加強了,在不修改核心業務方法上,咱們對它進行了擴展。證實了 AOP 可使輔助功能獨立於核心業務以外,方便了程序的擴展和解耦。編程

使用起來很方便,接下來一塊兒來看看 Spring 是如何實現 AOP 功能的吧~緩存


動態 AOP 自定義標籤

以前在介紹自定義標籤時,提到了 AOP 的實現也藉助了自定義註解,根據自定義標籤的思想:每一個自定義的標籤,都有對應的解析器,而後藉助強大的開發工具 IDEA 定位功能,找到解析器註冊的地方:安全

idea_find_file_source

  1. 按住 ctrl,定位標籤對應的 xsd 文件
  2. 根據命名文件,在 META-INF 目錄下找到了 spring.handlers 文件
  3. 在處理器文件中發現了處理器 AopNamespaceHandler
public class AopNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		// 註釋 8.1 自定義註解,註冊解析器,元素名是 aspectj-autoproxy
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}
}
複製代碼

處理器繼承自 NamespaceHandlerSupport,在加載過程當中,將會執行 init 初始化方法,在這裏,會註冊 aspectj-autoproxy 類型的解析器 AspectJAutoProxyBeanDefinitionParser

如何註冊自定義解析器以前也瞭解過了,因此接下來直接來看看,遇到 aspectj-autoproxy 類型的 bean,程序是如何解析的。


註冊 AnnotationAwareAspectJAutoProxyCreator

來看下解析時,它的入口方法以下:

public BeanDefinition parse(Element element, ParserContext parserContext) {
	// aop 註解的解析入口,註冊 AnnotationAwareAspectJAutoProxyCreator
	AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
	// 對註解中子類的處理
	extendBeanDefinition(element, parserContext);
	return null;
}
複製代碼

入口方法一如既往的簡潔,交代了要作的事情,而後具體複雜邏輯再交給工具類或者子類繼續實現,因此接下來要看的是如何註冊 AnnotationAwareAspectJAutoProxyCreator

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) {
	// 經過工具類,註冊或升級 AspectJAnnotationAutoProxyCreator
	BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			parserContext.getRegistry(), parserContext.extractSource(sourceElement));
	// 處理 proxy-target-class 以及 expose-proxy 屬性
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
	// 註冊組件並通知,讓監聽器進行處理
	registerComponentIfNecessary(beanDefinition, parserContext);
}
複製代碼

能夠看到這個方法內部有三個處理邏輯,因此咱們來一個一個去分析瞭解:


註冊或者升級 AnnotationAwareAspectJAutoProxyCreator

對於 AOP 的實現,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成,它能夠根據 @Point 註解定義的切點來自動代理相匹配的 bean

因爲 Spring 替咱們作了不少工做,因此開發 AOP 業務時才能夠這麼簡單,連配置也簡化了許多,因此來看下 Spring 是如何使用自定義配置來幫助咱們自動註冊 AnnotationAwareAspectJAutoProxyCreator

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) {
	// 實際註冊的 bean 類型是 AnnotationAwareAspectJAutoProxyCreator
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
		// 若是在 registry 已經存在自動代理建立器,而且傳入的代理器類型與註冊的不一致,根據優先級判斷是否須要修改
		BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
		if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
			// 根據優先級選擇使用哪個
			int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
			int requiredPriority = findPriorityForClass(cls);
			if (currentPriority < requiredPriority) {
				// 傳進來的參數優先級更大,修改註冊的 beanName,使用傳進來的代理建立器
				apcDefinition.setBeanClassName(cls.getName());
			}
		}
		// 由於已經存在代理器,不須要以後的默認設置,直接返回
		return null;
	}
	RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
	beanDefinition.setSource(source);
	// 默認的是最小優先級
	beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
	beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
	// 自動代理建立器的註冊名字永遠是 org.springframework.aop.config.internalAutoProxyCreator
	registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
	return beanDefinition;
}
複製代碼

這個步驟中,實現了自動註冊 AnnotationAwareAspectJAutoProxyCreator 類,同時能看到涉及到優先級的概念和註冊名一直都是 AUTO_PROXY_CREATOR_BEAN_NAME


處理 proxy-target-class 以及 expose-proxy 屬性

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
	if (sourceElement != null) {
		// 這方法做用挺簡單的,就是解析下面兩個屬性,若是是 true,將它們加入代理註冊器的屬性列表中
		// definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE)
		boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
		if (proxyTargetClass) {
			// 處理 proxy-target-class 屬性
			// 與代碼生成方式有關,在以後步驟中決定使用 jdk 動態代理 或 cglib
			AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
		}
		boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
		if (exposeProxy) {
			// 處理 expose-proxy 屬性
			// 擴展加強,有時候目標對象內部的自我調用沒法實施切面中的加強,經過這個屬性能夠同時對兩個方法進行加強
			AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
		}
	}
}
複製代碼

關於 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); 方法,它是一個屬性設置的過程,若是解析到的屬性爲 true,將它們加入代理註冊器的屬性列表中,這裏不細說下去。

將這兩個屬性分開熟悉:


proxy-target-class

Spring AOP 部分使用 JDK 動態代理 (Proxy + InvocationHandler),或者 CGLIB (Code Generation LIB)來爲目標對象建立代理。書中提到,推薦使用的是 JDK 動態代理。

若是被代理的目標對象實現了至少一個接口,則會使用 JDK 動態代理。全部該目標類型實現的接口都將被代理。

若該目標對象**沒有實現任何接口,則建立一個 CGLIB 代理。**若是但願代理目標對象的全部方法,而不僅是實現自接口的方法,能夠經過該屬性 proxy-target-class 開啓強制使用 CGLIB 代理。

可是強制開啓 CGLIB 會有如下兩個問題:

  • 沒法同時(advise)Final 方法,由於他們不能被覆寫
  • 須要將 CGLB 二進制發行包放在 classpath 下面

若是考慮好上面兩個方面,那就能夠經過如下兩個地方來強制開啓 CGLIB 代理:

<!-- one -->
<aop:config proxy-target-class="true">...</aop:config>
<!-- two -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
複製代碼

其中有關 CGLIB 代理,這位老哥講得很透徹,建議你們能夠去了解一下~ Cglib及其基本使用


expose-proxy

有時候目標對象內部的自我調用將沒法實施切面中的加強。

例如兩個方法都加上了事務註解 @Transactional 可是事務類型不同:

public interface TestService {
	void a();
	void b();
}

public class TestServiceImpl implements TestService {

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void a() {
		this.b();
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void b() {
		System.out.println("Hello world");
	}
}
複製代碼

此處的 this 指向了目標對象, this.b() 方法將不會執行 b 事務的切面,即不會執行事務加強。

爲了解決這個問題,使 a()b() 方法同時加強,能夠經過 expose-proxy 來實現:

<!-- one -->
<aop:config expose-proxy="true">...</aop:config>
<!-- two -->
<aop:aspectj-autoproxy expose-proxy="true"/>
複製代碼

註冊組件並通知

emmmm,這個方法內部邏輯如名字同樣清晰,因此不細說啦。


建立 AOP 代理

前面主要圍繞着自動代理器 AnnotationAwareAspectJAutoProxyCreator 的註冊流程來說解,接下來看自動代理器作了什麼來完成 AOP 的操做。

下面是 AnnotationAwareAspectJAutoProxyCreator 的繼承體系:

annotation_aware_aspectJ_auto_proxy_creator_diagram

在圖片右上角,發現它實現了 BeanPostProcessor 接口,以前文章提到過,它是一個後處理器,能夠在 bean 實例化先後進行擴展。查看了實現了該接口的兩個方法,postProcessBeforeInitialization 沒有作處理,直接返回該對象。

實際進行處理的是 postProcessAfterInitialization 方法,在 bean 實例化以後的處理,在這一步中進行裏代理加強,因此來看下這個方法:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		// 組裝 key
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			// 若是適合被代理,則須要封裝指定的 bean
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	// 若是已經處理過
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	// 不需加強
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	// 給定的 bean 類是否表明一個基礎設施類,基礎設置類不該代理 || 配置了指定 bean 不須要代理
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}
	// 若是存在加強方法則建立代理
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		// 加強方法不爲空
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		// 建立代理
		Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}
	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
}
複製代碼

來提取一下核心流程:

  1. 獲取加強方法或者加強器 咱們剛纔寫的 @Before@After 之類的,就是加強方法,AOP 處理時,要先找出這些加強方法。
  2. 根據獲取的加強進行代理 找到加強方法後,須要對這些加強方法進行加強代理,實際上這個 bean 已經不徹底是原來的類型了,會變成代理後的類型。

獲取加強方法或者加強器

入口方法在這裏:

protected Object[] getAdvicesAndAdvisorsForBean(
		Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
	// 尋找符合的切面
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	// 從 beanFactory 中獲取聲明爲 AspectJ 註解的類,對並這些類進行加強器的提取
	// 委派給子類實現 org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.extendAdvisors
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	// 尋找匹配的加強器
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
}
複製代碼

對於指定 bean 的加強方法的獲取包含這兩個步驟,獲取全部的加強以及尋找全部加強中適用於 bean 的加強並應用。對應於 findCandidateAdvisorsfindAdvisorsThatCanApply 這兩個方法。若是沒找到對應的加強器,那就返回 DO_NOT_PROXY ,表示不須要進行加強。

因爲邏輯太多,因此接下來貼的代碼不會太多,主要來了解它的大體流程,有須要的能夠跟着源碼工程的註釋跟蹤完整的流程~:


尋找對應的加強器 findCandidateAdvisors

protected List<Advisor> findCandidateAdvisors() {
	List<Advisor> advisors = super.findCandidateAdvisors();
	if (this.aspectJAdvisorsBuilder != null) {
		// 註釋 8.3 實際調用的是 org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
}
複製代碼

實際來看,關鍵是這個方法 this.aspectJAdvisorsBuilder.buildAspectJAdvisors() 這個方法看起來簡單,可是實際處理的邏輯不少,代碼深度也不少,因此爲了不太多代碼,我羅列了主要流程,和關鍵的處理方法作了什麼

主要流程以下:

  1. 獲取全部 beanName,會將以前在 beanFactory 中註冊的 bean 都提取出來。
  2. 遍歷前一步驟提取出來的 bean 列表,找出打上 @AspectJ 註解的類,進行進一步處理
  3. 繼續對前一步提取的 @AspectJ 註解的類進行加強器的提取
  4. 將提取結果加入緩存中

能夠查詢代碼中的註釋,從 [註釋 8.3] 到 [註釋 8.8 根據切點信息生成加強器] 都是這個方法的處理邏輯

※※在這個流程的最後一步中,會將識別到的切點信息(PointCut)和加強方法(Advice)進行封裝,具體是由 Advisor 的實現類 InstantiationModelAwarePointcutAdvisorImpl 進行統一封裝。

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
	// 簡單賦值
	this.declaredPointcut = declaredPointcut;
    ...

	if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
		this.pointcut = new PerTargetInstantiationModelPointcut(
		this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
		this.lazy = true;
	}
	else {
		// A singleton aspect.
		this.pointcut = this.declaredPointcut;
		this.lazy = false;
		// 初始化加強器
		this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
	}
}
複製代碼

封裝體前半部分邏輯只是簡單賦值。關鍵是這個方法 instantiateAdvice(this.declaredPointcut),在這一步中,對不一樣的加強(Before/After/Around)實現的邏輯是不同的。在 ReflectiveAspectJAdvisorFactory#getAdvice 方法中區別實現了根據不一樣的註解類型封裝不一樣的加強器。

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
		...
    // 註釋 8.7 根據不一樣的註解類型封裝不一樣的加強器
	switch (aspectJAnnotation.getAnnotationType()) {
		case AtPointcut:
			
		}
		return null;
	case AtAround:
		springAdvice = new AspectJAroundAdvice(
				candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
		break;
	case AtBefore:
		springAdvice = new AspectJMethodBeforeAdvice(
				candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
		break;
	case AtAfter:
		springAdvice = new AspectJAfterAdvice(
				candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
		break;
	case AtAfterReturning:
		springAdvice = new AspectJAfterReturningAdvice(
		    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
		AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
		if (StringUtils.hasText(afterReturningAnnotation.returning())) {
			springAdvice.setReturningName(afterReturningAnnotation.returning());
		}
		break;
	case AtAfterThrowing:
		springAdvice = new AspectJAfterThrowingAdvice(
		    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
		AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
	    if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
		    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
		}
		break;
	default:
	}		
}
複製代碼

最後切點方法經過解析和封裝成 Advisor,提取到的結果加入到緩存中。細心的你可能會發現**除了普通的加強器外,還有另外兩種加強器:同步實例化加強器和引介加強器。**因爲用的比較少,因此我看到源碼中這兩個分支處理沒有深刻去學習,感興趣的同窗請繼續深刻學習這兩種加強器~


獲取匹配的加強器 findAdvisorsThatCanApply

在前面流程中,已經完成了全部加強器的解析,可是對於前面解析到的加強器,並不必定都適用於當前處理的 bean,因此還須要經過一個方法來挑選出合適的加強器。

protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
	ProxyCreationContext.setCurrentProxiedBeanName(beanName);
	try {
		// 在這一步中進行過濾加強器
		return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
	}
	finally {
		ProxyCreationContext.setCurrentProxiedBeanName(null);
	}
}
複製代碼

能夠看到,具體實現過濾操做的是工具類方法 AopUtils.findAdvisorsThatCanApply:

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
	if (candidateAdvisors.isEmpty()) {
		return candidateAdvisors;
	}
	List<Advisor> eligibleAdvisors = new ArrayList<>();
	// 遍歷全部加強器
	for (Advisor candidate : candidateAdvisors) {
		// 首先處理引介加強
		if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
			eligibleAdvisors.add(candidate);
		}
	}
	boolean hasIntroductions = !eligibleAdvisors.isEmpty();
	for (Advisor candidate : candidateAdvisors) {
		// 前面處理過了,跳過
		if (candidate instanceof IntroductionAdvisor) {
			// already processed
			continue;
		}
		// 處理普通加強器類型
		if (canApply(candidate, clazz, hasIntroductions)) {
			eligibleAdvisors.add(candidate);
		}
	}
	return eligibleAdvisors;
}
複製代碼

具體判斷邏輯在 canApply() 方法中,若是判斷符合條件的,加入到 eligibleAdvisors 中,最後返回對於這個 bean 適合的加強器列表。


建立代理

經過前面的流程,獲取到了全部對應 bean 的加強器後,能夠開始代理的建立。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
	ProxyFactory proxyFactory = new ProxyFactory();
	// 拷貝,獲取當前類中的相關屬性
	proxyFactory.copyFrom(this);
	// 決定對於給定 bean 是否應該使用 targetClass 而不是他的接口代理
	if (!proxyFactory.isProxyTargetClass()) {
		// 檢查 proxyTargetClass 設置以及 preserveTargetClass 屬性
		if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			// 添加代理接口
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}
	// 這一步中,主要將攔截器封裝爲加強器
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	// 定製代理
	customizeProxyFactory(proxyFactory);
	// 用來控制代理工廠被配置以後,是否含容許修改通知
	// 缺省值爲 false,不容許修改代理的配置
	proxyFactory.setFrozen(this.freezeProxy);
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}
	// 生成代理,委託給了 ProxyFactory 去處理。
	return proxyFactory.getProxy(getProxyClassLoader());
}
複製代碼

對於代理類的建立和處理, Spring 委託給了 ProxyFactory 去處理,在上面貼出的函數主要是對 ProxyFactory 的初始化操做,進而對真正的建立代理作準備,主要流程以下:

  1. 獲取當前類的屬性
  2. 添加代理接口
  3. 封裝 Advisor 並加入到 ProxyFactory
  4. 設置要代理的類
  5. 爲子類提供定製的函數 customizeProxyFactory,子類經過該方法對 ProxyFactory 進行進一步的封裝
  6. 進行獲取代理操做

比較關鍵的是第三個步驟和第六個步驟,其中在第三個步驟中,進行的是攔截器包裝,詳細代碼流程請查 [註釋 8.9 爲給定的bean建立AOP代理] 和 [註釋 8.10 包裝攔截器,封裝成 Advisor]

接着,完成了全部加強器的封裝過程,到了解析的最後一步,進行代理的建立和獲取

public Object getProxy(@Nullable ClassLoader classLoader) {
	return createAopProxy().getProxy(classLoader);
}
複製代碼

建立代理 createAopProxy()

定位到建立代理的代碼:

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() || Proxy.isProxyClass(targetClass)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}
複製代碼

從上面代碼中能看出,根據了幾個關鍵屬性,判斷建立的是哪一種類型的 AopProxy,一種是 JDK 動態代理,另外一種是 CGLIB 動態代理。

前面提到過的 proxy-target-class 屬性和 targetClass 屬性,在這裏判斷了應該建立哪個代理。


獲取代理 getProxy()

觀察圖片以及前面分析,能夠知道有兩種代理方式:[JDK 動態代理] 和 [CGLIB 動態代理]

同時先說下動態代理的含義:抽象類在編譯期間是未肯定具體實現子類,在運行時才生成最終對象。


JDK 動態代理

JDK 代理是默認推薦的代理方式,使用的是 Proxy + InvocationHandler

能夠經過如下方式實現:定義一個接口、實現類,和一個處理器繼承於 InvocationHandler,而後重載處理器中的 invoke 方法,對代理對象進行加強。

JdkDynamicAopProxy.java

public Object getProxy(@Nullable ClassLoader classLoader) {
	// 註釋 8.11 JDK 動態代理
	if (logger.isTraceEnabled()) {
		logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
	}
	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
複製代碼

獲取代理的核心步驟在 Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),第三個參數是 JdkDynamicAopProxy 自己,並且它實現了 InvocationHandler 接口,重載了 invoke 方法。

org.springframework.aop.framework.JdkDynamicAopProxy#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	// 註釋 8.12 jdk 動態代理重載的 invoke 方法
	MethodInvocation invocation;
	Object oldProxy = null;
	boolean setProxyContext = false;
	TargetSource targetSource = this.advised.targetSource;
	Object target = null;
	try {
		Object retVal;
		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}
		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);
		// Get the interception chain for this method.
		// 獲取此方法的攔截鏈
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		// 檢查咱們是否有任何切面邏輯。若是咱們不這樣作,咱們能夠回退直接反射調用目標,並避免建立 MethodInvocation。
		if (chain.isEmpty()) {
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
		else {
			// 將攔截器封裝在 ReflectiveMethodInvocation,便於使用 proceed 執行攔截器
			invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);

			// 執行攔截器鏈
			retVal = invocation.proceed();
		}
		...
		return retVal;
	}
	finally {
		if (target != null && !targetSource.isStatic()) {
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			AopContext.setCurrentProxy(oldProxy);
		}
	}
}
複製代碼

建立 JDK 代理過程當中,主要的工做時建立了一個攔截器鏈,並使用 ReflectiveMethodInvocation 類進行封裝,封裝以後,逐一調用它的 proceed 方法, 用來實如今目標方法的前置加強和後置加強。

org.springframework.aop.framework.ReflectiveMethodInvocation#proceed

public Object proceed() throws Throwable {
	// 執行完全部加強器後執行切點方法
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		return invokeJoinpoint();
	}
	// 獲取下一個要執行的攔截器
	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// 動態匹配
		InterceptorAndDynamicMethodMatcher dm =
				(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
		Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
		if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
		else {
			// 匹配失敗,跳過攔截器,直接返回
			return proceed();
		}
	}
	else {
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
}
複製代碼

具體代碼和註釋請定位到該方法查看。關於 JDK 動態代理,深刻學習的話也能夠單獨拎出來,因此推薦看這篇資料 小豹子帶你看源碼:JDK 動態代理,進行了和學習


CGLIB 動態代理

CGLIB[Code Generation LIB] 是一個強大的高性能的代碼生成包。它普遍應用於許多 AOP 框架。

再次推薦參考資料一,這位老哥將 CGLIB 代理, 詳細介紹了 CGLIB 在什麼場景使用,以及被它加強後代碼處理順序,Cglib及其基本使用

但願看完這篇文章,能過了解到 CGLIB 代碼生成包具體是如何對類進行加強。


代理加強結果

經過前面一系列步驟,解析標籤、屬性、加強方法,到最後獲取 CGLIB 代理,經過代理建立 bean

來看下最後被代理的 bean 內部:

after_post_bean_processor

從圖中能夠看到,最終建立的是被修飾後的 bean,內部很明顯是 CGGLIB 代理生成的代碼,咱們在不修改業務代碼的狀況下,實現了方法加強。


靜態 AOP

既然有動態代理,那麼也會有靜態代理。

使用靜態 AOP 的時候,須要用到 LTW (Load-Time Weaving 加載時織入),指的是在虛擬機載入字節碼文件時動態織入 AspectJ 切面。

AOP 的靜態代理主要是在虛擬機啓動時經過改變目標對象字節碼的方式來完成對目標對象的加強,它與動態代理相比具備更高的效率,由於在動態代理調用的過程當中,還須要一個動態建立代理類並代理目標對象的步驟,而靜態代理則是在啓動時便完成了字節碼增減,當系統再次調用目標類時,與調動正常的類並沒有區別,因此在效率上會相對高些。

關於靜態 AOP 的使用和學習,能夠參考這篇文章:從代理機制到Spring AOP


總結

動態 AOP 使用起來很簡單,對於如何實現,總結起來就兩點:

  1. 動態解析 AOP 標籤
  2. 建立 AOP 代理

但在 Spring 底層實現邏輯倒是複雜到不行,從 Spring 框架中能夠看到這是良好的代碼設計思路,頂層入口儘可能簡單,使用者很容易就能掌握該功能,複雜實現邏輯都被隱藏了。

寫這一篇 AOP 學習總結,花了將近一週,先看了一遍書籍, 下班後花了一晚,將大體流程理了一遍,次日晚上走讀代碼,發現有些地方還存在疑惑,例如 JDKcglib 動態代理是怎麼回事,翻閱查詢資料,弄懂後又過了一天。

將代碼註釋加上,分析動態代理每個步驟作的事情,結合以前學的後處理器 BeanPostProcessor 知識和自定義標籤解析知識一塊兒又梳理一遍。零零散散,終於整理完成。

在靜態 AOP 知識點,按照個人理解,越往系統底層深刻,它的執行效率越高,因此減小了動態建立代理類和代理目標對象的步驟,靜態代理的速度會獲得提高。同時因爲接近底層後,代碼編寫的複雜度一樣會增長,因此我在權衡高頻率使用場景(動態代理),本次學習沒有詳細去了解,留下這個坑,之後有機會再填吧~


因爲我的技術有限,若是有理解不到位或者錯誤的地方,請留下評論,我會根據朋友們的建議進行修正

Gitee 地址 https://gitee.com/vip-augus/spring-analysis-note.git

Github 地址 https://github.com/Vip-Augus/spring-analysis-note


參考資料

  1. Cglib及其基本使用

  2. 說說 cglib 動態代理

  3. Spring-AOP 自動建立代理

  4. 小豹子帶你看源碼:JDK 動態代理

  5. 從代理機制到Spring AOP

  6. Spring 源碼深度解析 / 郝佳編著. -- 北京 : 人民郵電出版社


傳送門:

相關文章
相關標籤/搜索