1. 程式人生 > >SpringAOP+原始碼解析,切就完事了

SpringAOP+原始碼解析,切就完事了

本文是對近期學習知識的一個總結,附帶原始碼註釋及流程圖,如有不足之處,還望評論區批評指正。 [toc] 此處感謝javadoop的原始碼解析,收益匪淺:[https://javadoop.com/post/spring-aop-intro](https://javadoop.com/post/spring-aop-intro) # 一、AOP、SpringAOP、AspectJ的區別 AOP為`Aspect Oriented Programming`的縮寫,意為:**面向切面程式設計**,通過**預編譯方式和執行期間動態代理**實現程式功能的統一維護的一種技術。利用AOP可以**對業務邏輯的各個部分進行隔離**,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。 文縐縐的,沒用過確實很懵,但是用過之後,不說清晰,起碼有內意思了。 關於SpringAOP和AspectJ,參考Javadoop老師的解釋:[https://javadoop.com/post/spring-aop-intro](https://javadoop.com/post/spring-aop-intro) > **Spring AOP:** > > - 它基於動態代理來實現。預設地,如果使用介面的,用 JDK 提供的動態代理實現,如果沒有介面,使用 CGLIB 實現。大家一定要明白背後的意思,包括什麼時候會不用 JDK 提供的動態代理,而用 CGLIB 實現。 > - Spring 3.2 以後,spring-core 直接就把 CGLIB 和 ASM 的原始碼包括進來了,這也是為什麼我們不需要顯式引入這兩個依賴 > - Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依賴於 IOC 容器來管理。 > - 如果你是 web 開發者,有些時候,你可能需要的是一個 Filter 或一個 Interceptor,而不一定是 AOP。 > - Spring AOP 只能作用於 Spring 容器中的 Bean,它是使用純粹的 Java 程式碼實現的,只能作用於 bean 的方法。 > - Spring 提供了 AspectJ 的支援,後面我們會單獨介紹怎麼使用,一般來說我們用**純的** Spring AOP 就夠了。 > - 很多人會對比 Spring AOP 和 AspectJ 的效能,Spring AOP 是基於代理實現的,在容器啟動的時候需要生成代理例項,在方法呼叫上也會增加棧的深度,使得 Spring AOP 的效能不如 AspectJ 那麼好。 > > **AspectJ:** > > - AspectJ 出身也是名門,來自於 Eclipse 基金會,link:https://www.eclipse.org/aspectj > > - 屬於靜態織入,它是通過修改程式碼來實現的,它的織入時機可以是: > - Compile-time weaving:編譯期織入,如類 A 使用 AspectJ 添加了一個屬性,類 B 引用了它,這個場景就需要編譯期的時候就進行織入,否則沒法編譯類 B。 > - Post-compile weaving:也就是已經生成了 .class 檔案,或已經打成 jar 包了,這種情況我們需要增強處理的話,就要用到編譯後織入。 > - **Load-time weaving**:指的是在載入類的時候進行織入,要實現這個時期的織入,有幾種常見的方法。1、自定義類載入器來幹這個,這個應該是最容易想到的辦法,在被織入類載入到 JVM 前去對它進行載入,這樣就可以在載入的時候定義行為了。2、在 JVM 啟動的時候指定 AspectJ 提供的 agent:`-javaagent:xxx/xxx/aspectjweaver.jar`。 > > - AspectJ 能幹很多 Spring AOP 幹不了的事情,它是 **AOP 程式設計的完全解決方案**。Spring AOP 致力於解決的是企業級開發中最普遍的 AOP 需求(方法織入),而不是力求成為一個像 AspectJ 一樣的 AOP 程式設計完全解決方案。 > - 因為 AspectJ 在實際程式碼執行前完成了織入,所以大家會說它生成的類是沒有額外執行時開銷的。 # 二、AOP關鍵術語 ![](https://img-blog.csdnimg.cn/2020050115000823.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) - 切面(Aspect):也就是我們定義的專注於提供輔助功能的模組,比如安全管理,日誌資訊等。 - 連線點(JoinPoint):切面程式碼可以通過連線點切入到正常業務之中,圖中每個方法的每個點都是連線點。 - 切入點(PointCut):一個切面不需要通知所有的連線點,而**在連線點的基礎之上增加切入的規則**,選擇需要增強的點,最終真正通知的點就是切入點。 - 通知方法(Advice):就是切面需要執行的工作,主要有五種通知:before,after,afterReturning,afterThrowing,around。 - 織入(Weaving):將切面應用到目標物件並建立代理物件的過程,SpringAOP選擇再目標物件的執行期動態建立代理對 - 引入(introduction):在不修改程式碼的前提下,引入可以在執行期為類動態地新增方法或欄位。 # 三、通知的五種型別 - 前置通知Before:目標方法呼叫之前執行的通知。 - 後置通知After:目標方法完成之後,無論如何都會執行的通知。 - 返回通知AfterReturning:目標方法成功之後呼叫的通知。 - 異常通知AfterThrowing:目標方法丟擲異常之後呼叫的通知。 - 環繞通知Around:可以看作前面四種通知的綜合。 # 四、切入點表示式 上面提到:連線點增加切入規則就相當於定義了切入點,當然切入點表示式分為兩種:within和execution,這裡主要學習execution表示式。 - 寫法:execution(訪問修飾符 返回值 包名.包名……類名.方法名(引數列表)) - 例:`execution(public void com.smday.service.impl.AccountServiceImpl.saveAccount())` - 訪問修飾符可以省略,返回值可以使用萬用字元*匹配。 - 包名也可以使用`*`匹配,數量代表包的層級,當前包可以使用`..`標識,例如`* *..AccountServiceImpl.saveAccount()` - 類名和方法名也都可以使用`*`匹配:`* *..*.*()` - 引數列表使用`..`可以標識有無引數均可,且引數可為任意型別。 > 全通配寫法:`* *…*.*(…)` 通常情況下,切入點應當設定再業務層實現類下的所有方法:`* com.smday.service.impl.*.*(..)`。 # 五、AOP應用場景 - 記錄日誌 - 監控效能 - 許可權控制 - 事務管理 # 六、AOP原始碼分析 ## SpringBean的生命週期 寫了好多篇文章,每次都要來回顧一下SpringBean的生命週期,可見它真的十分重要。 - [Spring的完整生命週期。](https://www.cnblogs.com/summerday152/p/13639896.html#bean-%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - [Spring解決迴圈依賴,對例項化之後,初始化之前動了手腳。](https://www.cnblogs.com/summerday152/p/13648377.html#springbean%E7%9A%84%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B) Spring的Aop又是在哪完成的對目標物件的代理呢?我們大概也能夠想到,其實就是在執行回撥的時候。按照慣例,先複習一下,從getBean開始到返回Bean經歷了什麼: ![](https://img2020.cnblogs.com/blog/1771072/202009/1771072-20200911172406955-617808414.png) 回顧完SpringBean的建立流程之後,我們以註解方式`@EnableAspectJAutoProxy`配置Aop開啟@Aspectj為例,進行一波AOP的流程總結: ## AOP的流程總結 通過原始碼可以發現,其實是通過`@EnableAspectJAutoProxy`註解注入了一個`AnnotationAwareAspectJAutoProxyCreator`,但這個類中其實並沒有重寫`postProcessAfterInitialization()`,最終實現其實是在`AbstractAutoProxyCreator`中。 具體乾的事情,我已經通過一張圖總結出來了,如果想要了解更加具體的資訊,不妨開啟原始碼,可以看的更加清晰一些。 ![](https://img2020.cnblogs.com/blog/1771072/202009/1771072-20200911172534811-1175507146.png) ### AnnotationAwareAspectJAutoProxyCreator的註冊 首先是對`AnnotationAwareAspectJAutoProxyCreator`的註冊環節:【在此不作贅述】 ```java class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable // 1. 註冊proxy creator public BeanDefinition parse(Element element, ParserContext parserContext) { AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); extendBeanDefinition(element, parserContext); return null; } } ``` ### applyBeanPostProcessorsAfterInitialization入口 AbstractAutowireCapableBeanFactory.java ```java protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { //如果bean實現了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware介面, 回撥 invokeAwareMethods(beanName, bean); Object wrappedBean = bean; //aop在init-method之前並沒有進行操作, 目前還是原來那個物件 if (mbd == null || !mbd.isSynthetic()) { //BeanPostProcessor 的 postProcessBeforeInitialization 回撥 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } //處理bean中定義的init-method或 bean實現了InitializingBean ,呼叫afterPropertiesSet() 方法 invokeInitMethods(beanName, wrappedBean, mbd); if (mbd == null || !mbd.isSynthetic()) { //BeanPostProcessor 的 postProcessAfterInitialization 回撥 注意這裡! wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } ``` ```java @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { //AnnotationAwareAspectJAutoProxyCreator Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;//返回[可能代理後的]結果 } ``` ### AbstractAutoProxyCreator的主要方法 ```java //SpringAop在IOC容器建立bean例項的最後對bean進行處理,進行代理增強, AbstractAutoProxyCreato @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey);//這個方法將返回代理類 } } return bean; } ``` ```java protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { //返回匹配當前bean 的所有的advisor, advice, interceptor 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; } ``` ### createProxy過程 ```java protected Object createProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { //建立ProxyFactory例項 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); //在schema-based配置方式裡,可以將 proxy-target-class="true",這樣不管有沒有介面都使用cglib if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } //返回當前bean的advisors陣列 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); //設定advisors陣列 proxyFactory.setTargetSource(targetSource);//targetSource 攜帶了真實實現的資訊 customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } return proxyFactory.getProxy(getProxyClassLoader());//getProxy(getProxyClassLoader())這一步建立代理 } ``` ### JDK動態代理和CGLIB動態代理何時使用 這一步產生分歧的地方在ProxyFactory的getProxy方法,在getProxy之前,首先需要執行createAopProxy,而createAopProxy方法又被這個AopProxyFactory呼叫: ```java protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } //建立AopProxy之前,需要一個AopProxyFactory return getAopProxyFactory().createAopProxy(this); } // ProxyCreatorSupport //這個aopProxyFactory用於建立aopProxy, 之後可以用aopProxy.getProxy(classLoader)建立代理 public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); } ``` 也就是最後會走到DefaultAopProxyFactory中 ```java @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (!IN_NATIVE_IMAGE && (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException(); } //如果要代理的類本身就是介面,使用JDK動態代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //jdk動態代理基於介面,只有介面中的方法才會被增強, cglib基於類繼承,如果方法使用了final或者private修飾,也不能增強 return new ObjenesisCglibAopProxy(config); } else { // 如果有介面,會跑到這個分支 return new JdkDynamicAopProxy(config); } } ``` 總結: - 如果被代理的目標實現了一個或多個自定義的介面,那麼就會使用JDK動態代理。 - 如果沒有實現任何介面,則使用CGLIB實現代理。 - 如果設定`proxy-target-class=true `或`
`則不管有沒有實現介面都會使用CGLIB。 # 七、JDK動態代理的實現 最終的最終,都會走到真正建立代理物件的流程上: ```java @Override public Object getProxy(@Nullable ClassLoader classLoader) { //獲取代理介面 Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); //查詢代理目標的介面是否定義equals和hashcode方法 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); //使用jdk動態代理建立代理物件 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } ``` 第一個引數:classLoader。 第二個引數:實現的介面。 第三個引數:InvocationHandler例項。 而本身JdkDynamicAopProxy本就實現了InvocationHandler,因此傳入this。至此,當呼叫被代理類的方法的時候,都會最終呼叫代理類實現的invoke方法,在這個方法中定義橫切的邏輯。 ```java public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } ``` - proxy:代理物件的引用。 - method:當前執行的方法。 - args:當前執行方法所需的引數。 - return:和被代理物件有相同的返回值。 ```java @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //當生成的代理類對外提供服務的時候,都會匯入到這個invoke方法中 Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // 對equals方法的代理 return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { //對hashCode()方法的代理 return hashCode(); } //... Object retVal; //如果設定了exposeProxy,將proxy放入ThreadLocal中 if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); Class targetClass = (target != null ? target.getClass() : null); // 獲取目標方法的攔截鏈,包含所有要執行的 advice List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // 檢查一下這個鏈上是不是有advice,如果沒有的話,可以跳過建立MethodInvocation if (chain.isEmpty()) { //chain如果是空的,表示不需要被增強,直接呼叫目標方法 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // 如果chain裡有advice 執行方法,得到返回值 MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //沿著攔截器鏈,執行通知 retVal = invocation.proceed(); } // 對返回值的處理 Class returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException(); } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // 釋放目標物件 targetSource.releaseTarget(target); } if (setProxyContext) { // 儲存代理物件 AopContext.setCurrentProxy(oldProxy); } } } ``` # 八、總結 > 以@AspectJ註解方式為例 - 首先,依據``或`@EnableAspectJAutoProxy`,Spring會在容器啟動的時候註冊名叫`internalAutoProxyCreator`的`AnnotationAwareAspectJAutoProxyCreator `。 - 在bean例項化完成,屬性裝配完成之後,開始執行回撥方法,這時取出所有的BeanPostProcessor,執行其postProcessAfterInitialization方法,準備開始對目標物件代理的建立。 - 首先建立一個代理工廠ProxyFactory,設定一系列的屬性,如所有的通知方法,增強器,攔截器和目標類等注入代理工廠,再呼叫ProxyFactory.getProxy(classLoader)獲取代理。 - 通過判斷是用JDK動態代理還是CGLIB建立不同的AopProxy,最後獲取getProxy。 > 參考資料 > > - [https://www.cnblogs.com/aobing/p/12849591.html](https://www.cnblogs.com/aobing/p/12849591.html) > - [https://javadoop.com/post/spring-aop-source](https://javadoop.com/post/spring-aop