從動態代理到Spring AOP(中)
一.前言
上一章節主要介紹了JDK動態代理和CGLIB動態代理:https://www.cnblogs.com/GrimMjx/p/11194283.html
這一章主要結合我們之前學習的動態代理的基礎來學習Sring AOP,本章學習需要Spring IOC的基礎。首先會有一個Spring AOP的例子,後面逐漸深入會把一些關鍵的原始碼貼出來供大家學習。
二.一個栗子
2.1 建立Spring配置檔案
本例子使用xml的方式來配置Spring,如果你用Springboot可以用@EnableAspectJAutoProxy來開啟AOP功能。xml配置Spring是否使用註解AOP的方式是<aop:aspectj-autoproxy/>,配置檔案中有這句話Spring就會支援註解的AOP。
2.2 建立要被代理的bean
這個bean的某個方法可能封裝著核心邏輯,如果我們想對這個方法的前後加入日誌或者其他邏輯進行增強,直接修改這個bean不符合面向物件的設計,還好Spring AOP幫我們做到這一點。那我們先建立一個簡單的bean:
2.3 建立Advisor
Spring2.0可以採用@AspectJ註解對POJO進行標記,從而定義一個包含切點資訊和增強橫切邏輯的切面然後織入匹配的目標Bean中
在AspectJConfig中,我們簡單的對test方法前後記錄日誌。記住光用@AspectJ註解是不夠的。要麼再新增@Component註解要麼在xml新增這個bean,官方解釋如下:
You may register aspect classes as regular beans in your Spring XML configuration, or autodetect them through classpath scanning - just like any other Spring-managed bean. However, note that the @Aspect annotation is not sufficient for autodetection in the classpath: For that purpose, you need to add a separate @Component annotation (or alternatively a custom stereotype annotation that qualifies, as per the rules of Spring’s component scanner).
再來看一下程式碼:
2.4 測試
我們可以寫一個簡單的測試類,通過容器拿到TestBean這個bean,然後呼叫test方法,看一下結果:
三.原始碼賞析
最好帶著問題去看原始碼,要不然自己也是跟著走一遍,原始碼是無盡,開發思想是有盡的。比如今天看了ConcurrentHashMap的size方法對cas的優化,再看看LongAdder是咋玩的,學到了分散思想。
-誰來建立AOP的?
-誰來解析@Aspect註解的類?
-JDK動態代理怎麼建立的?
-CGlib動態代理怎麼建立的?
-有什麼好的設計方式嗎?
-等等
3.1 誰來建立?
是AnnotationAwareAspectJAutoProxyCreator。(名字就很通俗易懂)
Spring掃到<aop:aspectj-autoproxy/>後,AspectJAutoProxyBeanDefinitionParser會註冊這位建立者。對於Spring AOP的實現,AnnotationAwareAspectJAutoProxyCreator是負責代理的建立者,也是我們賞析的開始。先來看下這個建立者的類圖:
當Spring載入每個Bean的時候會在例項化前呼叫postProcessorAfterInitialization方法,對於AOP的邏輯也由此開始:
3.2 獲取所有增強器
還記得之前的栗子嗎?有個類是有@AspectJ註解的AspectJConfig類,這個類裡面有@PointCut註解,這個註解的意思是對哪些方法進行增強,這裡@Pointcut("execution(* *.test(..))")表示要對所有test方法進行增強。通過不同的切點表達函式可以實現對某些你想要類或者方法進行增強。
那麼,什麼叫增強器?Spring AOP在JoinPoint“周圍”維護一系列的攔截器。有哪些Advice呢?
- @Before - 在JoinPoint方法之前執行
- @AfterReturning - 在JoinPoint方法正常執行後執行
- @AfterThrowing - 在JoinPoint方法丟擲異常退出並執行
- @After - 無論JoinPoint方法正常返回還是異常返回都會執行
- @Around - 在JoinPoint方法前後執行
例子中,有2個增強器。那我們看下原始碼是如何拿到所有的增強器的,看這個方法:org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
1 /** 2 * Look for AspectJ-annotated aspect beans in the current bean factory, 3 * and return to a list of Spring AOP Advisors representing them. 4 * <p>Creates a Spring Advisor for each AspectJ advice method. 5 * @return the list of {@link org.springframework.aop.Advisor} beans 6 * @see #isEligibleBean 7 */ 8 public List<Advisor> buildAspectJAdvisors() { 9 List<String> aspectNames = null; 10 synchronized (this) { 11 aspectNames = this.aspectBeanNames; 12 if (aspectNames == null) { 13 List<Advisor> advisors = new LinkedList<Advisor>(); 14 aspectNames = new LinkedList<String>(); 15 // 獲取容器內所有的beanName 16 String[] beanNames = 17 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false); 18 // for迴圈,找出增強方法 19 for (String beanName : beanNames) { 20 // 過濾不合法的bean,子類實現 21 if (!isEligibleBean(beanName)) { 22 continue; 23 } 24 // We must be careful not to instantiate beans eagerly as in this 25 // case they would be cached by the Spring container but would not 26 // have been weaved 27 // 獲取到bean型別 28 Class beanType = this.beanFactory.getType(beanName); 29 if (beanType == null) { 30 continue; 31 } 32 // 如果存在@AspectJ註解 33 if (this.advisorFactory.isAspect(beanType)) { 34 // 加入list 35 aspectNames.add(beanName); 36 AspectMetadata amd = new AspectMetadata(beanType, beanName); 37 if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { 38 MetadataAwareAspectInstanceFactory factory = 39 new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); 40 41 // 解析獲取增強方法 42 List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); 43 if (this.beanFactory.isSingleton(beanName)) { 44 // 放入快取 45 this.advisorsCache.put(beanName, classAdvisors); 46 } 47 else { 48 this.aspectFactoryCache.put(beanName, factory); 49 } 50 advisors.addAll(classAdvisors); 51 } 52 else { 53 // Per target or per this. 54 if (this.beanFactory.isSingleton(beanName)) { 55 throw new IllegalArgumentException("Bean with name '" + beanName + 56 "' is a singleton, but aspect instantiation model is not singleton"); 57 } 58 MetadataAwareAspectInstanceFactory factory = 59 new PrototypeAspectInstanceFactory(this.beanFactory, beanName); 60 this.aspectFactoryCache.put(beanName, factory); 61 advisors.addAll(this.advisorFactory.getAdvisors(factory)); 62 } 63 } 64 } 65 this.aspectBeanNames = aspectNames; 66 return advisors; 67 } 68 } 69 if (aspectNames.isEmpty()) { 70 return Collections.EMPTY_LIST; 71 } 72 List<Advisor> advisors = new LinkedList<Advisor>(); 73 for (String aspectName : aspectNames) { 74 // 從緩衝拿出增強器 75 List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName); 76 if (cachedAdvisors != null) { 77 advisors.addAll(cachedAdvisors); 78 } 79 else { 80 MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); 81 advisors.addAll(this.advisorFactory.getAdvisors(factory)); 82 } 83 } 84 return advisors; 85 }
到此我們找到了所有宣告@AspectJ註解的類,接下來是不是該找用@Before,@After等等那些註解的方法了?
等等,我們要的是Advisor,所有增強由Advisor的實現類InstantiationModelAwarePointcutAdvisorImpl封裝,不同的註解會封裝成不同的增強器。
1 public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp, 2 MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) { 3 /** 檢查開始 */ 4 Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass(); 5 validate(candidateAspectClass); 6 // 之前講過,獲取到方法上的註解資訊 7 AspectJAnnotation<?> aspectJAnnotation = 8 AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); 9 if (aspectJAnnotation == null) { 10 return null; 11 } 12 // If we get here, we know we have an AspectJ method. 13 // Check that it's an AspectJ-annotated class 14 if (!isAspect(candidateAspectClass)) { 15 throw new AopConfigException("Advice must be declared inside an aspect type: " + 16 "Offending method '" + candidateAdviceMethod + "' in class [" + 17 candidateAspectClass.getName() + "]"); 18 } 19 if (logger.isDebugEnabled()) { 20 logger.debug("Found AspectJ method: " + candidateAdviceMethod); 21 } 22 /** 檢查結束 */ 23 24 // 根據不同的註解生成不同的增強器 25 AbstractAspectJAdvice springAdvice; 26 switch (aspectJAnnotation.getAnnotationType()) { 27 case AtBefore: 28 springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif); 29 break; 30 case AtAfter: 31 springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif); 32 break; 33 case AtAfterReturning: 34 springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif); 35 AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); 36 if (StringUtils.hasText(afterReturningAnnotation.returning())) { 37 springAdvice.setReturningName(afterReturningAnnotation.returning()); 38 } 39 break; 40 case AtAfterThrowing: 41 springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif); 42 AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); 43 if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { 44 springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); 45 } 46 break; 47 case AtAround: 48 springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif); 49 break; 50 case AtPointcut: 51 if (logger.isDebugEnabled()) { 52 logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); 53 } 54 return null; 55 default: 56 throw new UnsupportedOperationException( 57 "Unsupported advice type on method " + candidateAdviceMethod); 58 } 59 // Now to configure the advice... 60 springAdvice.setAspectName(aspectName); 61 springAdvice.setDeclarationOrder(declarationOrderInAspect); 62 String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); 63 if (argNames != null) { 64 springAdvice.setArgumentNamesFromStringArray(argNames); 65 } 66 springAdvice.calculateArgumentBindings(); 67 return springAdvice; 68 }
3.3 獲取匹配的增強器
前面講了獲取所有的增強器,不一定都適用於現在的bean,我們要選出合適的增強器,也就是滿足配置的增強器,具體方法在:org.springframework.aop.support.AopUtils#findAdvisorsThatCanApply:
3.4 建立代理
首先看這個方法:org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy
那麼接下來就是代理的建立與獲取了:
我們先看下如何建立代理的,註釋如圖:
所以我們可以得出以下結論:
- 如果目標類實現了介面,預設使用JDK動態代理實現AOP,但是也可以強制使用CGLIB實現AOP
- 如果目標類沒有實現介面,那麼必須採用CGLIB庫
3.5 呼叫過程
最後就是方法呼叫的關鍵方法了,可以說最核心的方法就是這個了,org.springframework.aop.framework.ReflectiveMethodInvocation#proceed:
Spring aop的精華都在於此,核心就是遞迴思想,呼叫完了攔截鏈(所有增強器)中的所有攔截器方法後,再呼叫目標物件的方法
到底invoke方法是啥呢?見下圖,我們可以看到是一個介面的方法,不同增強器有不同的實現,我們這裡就看Before和After的:
最後畫一張圖吧,這樣比較好理解一點:
四.總結
過程就是先拿出所有適用的Adivsors,然後構造攔截鏈(chain),最後進行序列呼叫(遞迴)。
最後還是希望多寫幾個demo來實踐一下,打打斷點。還是那句話,原始碼是看不完的,最重要的是思想。這是我第一家公司技術總監老羊說的。我覺得還是挺收益的。本文還有很多不足之處,如果有寫的不對的地方還請指點一下,感