<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          隨手記 : AOP 如何避開 BeanNotOfRequiredTypeException 及 CGLIB

          共 3666字,需瀏覽 8分鐘

           ·

          2021-10-12 13:49

          一 . 前言

          今天對 Spring 進(jìn)行深度使用的時(shí)候 , 想仿照 AOP 去實(shí)現(xiàn)對應(yīng)的代理 , 但是卻觸發(fā)了 BeanNotOfRequiredTypeException 異常 , 原因是因?yàn)?Spring 會(huì)進(jìn)行類的校驗(yàn)

          于是突然產(chǎn)生了好奇 , 決定研究一下 , AOP 是通過什么方式避開這個(gè)校驗(yàn)過程

          二 . 前置知識(shí)

          • AOP 通過 AopProxy 進(jìn)行代理

          • SpringBoot 1.5 默認(rèn)使用 JDK Proxy , SpringBoot 2.0 基于自動(dòng)裝配(AopAutoConfiguration)的配置 , 默認(rèn)使用 CGlib

          JDK Proxy 和 CGLib 的區(qū)別

          老生常談的問題 , 問了完整性(湊字?jǐn)?shù)) , 還是簡單列一下 :

          • JDK Proxy : 利用攔截器(攔截器必須實(shí)現(xiàn)InvocationHanlder)加上反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。

          • CGLIB動(dòng)態(tài)代理:利用ASM開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。

          PS : 通過 proxy-target-class 可以進(jìn)行配置

          三 . 原理探索

          常規(guī)方式是 CGLIB , 所以主流程還是通過這種方式分析 , 有了前置知識(shí)的補(bǔ)充 , 實(shí)現(xiàn)猜測是由于 CGLIB 的特性 , 實(shí)際上被校驗(yàn)出來.

          • 源頭 :autowired 導(dǎo)入得時(shí)候會(huì)校驗(yàn)注入的類是否正確

          3.1 攔截的入口

          • Step 1 : AbstractAutowireCapableBeanFactory # populateBean

          • Step 2 : AutowiredAnnotationBeanPostProcessor # postProcessProperties

          • Step 3 : InjectionMetadata # inject

          • Step 4 : AutowiredAnnotationBeanPostProcessor # inject

          • Step 5 : DefaultListableBeanFactory # doResolveDependency

          public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
          @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter)
          throws BeansException
          {

          //............ 以下是主要邏輯

          if (autowiredBeanNames != null) {
          autowiredBeanNames.add(autowiredBeanName);
          }

          // 獲取 Autowired 的實(shí)際對象或者代理對象
          if (instanceCandidate instanceof Class) {
          instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
          }

          // 判斷該對象是否為null
          Object result = instanceCandidate;
          if (result instanceof NullBean) {
          if (isRequired(descriptor)) {
          raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
          }
          result = null;
          }

          // 核心攔截邏輯
          if (!ClassUtils.isAssignableValue(type, result)) {
          throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
          }
          return result;
          }
          }
          復(fù)制代碼

          3.2 攔截的判斷

          public static boolean isAssignable(Class lhsType, Class rhsType) {

          // 類型判斷
          if (lhsType.isAssignableFrom(rhsType)) {
          return true;
          } else {
          Class resolvedWrapper;

          // 基本類型特殊處理
          if (lhsType.isPrimitive()) {
          resolvedWrapper = (Class)primitiveWrapperTypeMap.get(rhsType);
          return lhsType == resolvedWrapper;
          } else {
          resolvedWrapper = (Class)primitiveTypeToWrapperMap.get(rhsType);
          return resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper);
          }
          }
          }
          復(fù)制代碼

          3.3 AOP 的使用

          看到了攔截的入口 , 那就得看看 AOP 中是如何通過 PostProcessor 進(jìn)行處理的了 , 首先看一下 PostProcessor 鏈表

          Step 1 : 當(dāng)對象 A 中字段是 @Autowired 注入的 AOP 代理類時(shí)

          此時(shí)我們可以發(fā)現(xiàn) , 在 DefaultListableBeanFactory # doResolveDependency 環(huán)節(jié)會(huì)去獲取該代理類的對象 , 拿到的對象如下圖所示 :

          // doResolveDependency 中獲取對象環(huán)節(jié)
          if (instanceCandidate instanceof Class) {
          // 此時(shí)拿到的對象就是一個(gè) cglib 代理類
          instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
          復(fù)制代碼

          Step 2 : 判斷類的關(guān)系入口

          // doResolveDependency 中判斷類的關(guān)系 -> true
          if (!ClassUtils.isAssignableValue(type, result)) {
          throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
          }

          // result.getClass()
          - name=com.gang.aop.demo.service.StartService$$EnhancerBySpringCGLIB$$d673b902
          復(fù)制代碼

          Step 3 : 判斷類的關(guān)系邏輯

          C- ClassUtils
          public static boolean isAssignable(Class lhsType, Class rhsType)
          {

          // 核心語句 , native 方法 -> public native boolean isAssignableFrom(Class cls);
          if (lhsType.isAssignableFrom(rhsType)) {
          return true;
          }
          //.........
          }

          // 這里簡單做了一個(gè)繼承類 , ChildService extends ChildService
          : ------> ChildService By ParentService :false <-------
          : ------> ParentService By ChildService:true <-------

          復(fù)制代碼

          由此可見 cglib 創(chuàng)建的對象滿足該條件 : 相同 , 或者是超類或者超接口

          這里回過頭看之前的問題 , 就很簡單了 :

          // 問題原因 : 
          我通過實(shí)現(xiàn) postProcessor 去做了一個(gè)代理
          public class AopProxyImpl extends Sourceable {
          private Sourceable source;
          }

          // 修改后 :
          public class AopProxyImpl extends Source {
          private Sourceable source;
          }

          // 通過繼承即可解決 BeanNotOfRequiredTypeException ,弄懂了就沒什么難度了
          //

          復(fù)制代碼

          四 . 深入原理

          那么繼續(xù)回顧下 CGLIB 的創(chuàng)建過程 , 實(shí)際上在編譯的結(jié)果上是可以很直觀的看到代理的對象的 :

          關(guān)于 CGLIB 的基礎(chǔ) , 可以看看菜鳥的文檔 CGLIB(Code Generation Library) 介紹與原理 , 寫的很詳細(xì)

          4.1 CGLIB 的創(chuàng)建過程

          FastClass 的作用

          FastClass 就是給每個(gè)方法編號(hào),通過編號(hào)找到方法,這樣可以避免頻繁使用反射導(dǎo)致效率比較低

          CGLIB 會(huì)生成2個(gè) fastClass :

          • xxxx$$FastClassByCGLIB$$xxxx ?:為生成的代理類中的每個(gè)方法建立了索引

          • xxxx$$EnhancerByCGLIB$$xxxx$$FastClassByCGLIB$$xxxx : 為我們被代理類的所有方法包含其父類的方法建立了索引

          原因 : cglib代理基于繼承實(shí)現(xiàn),父類中非public、final的方法無法被繼承,所以需要一個(gè)父類的fastclass來調(diào)用代理不到的方法

          FastClass 中有2個(gè)主要的方法 :

          // 代理方法
          public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
          final CglibService cglibService = (CglibService)o;
          switch (n) {
          case 0: {
          // 代理對應(yīng)的業(yè)務(wù)方法
          cglibService.run();
          return null;
          }
          case 1: {
          // 代理 equeals 方法
          return new Boolean(cglibService.equals(array[0]));
          }
          case 2: {
          // 代理 toString 方法
          return cglibService.toString();
          }
          case 3: {
          // 代理 hashCode 方法
          return new Integer(cglibService.hashCode());
          }
          }
          throw new IllegalArgumentException("Cannot find matching method/constructor");
          }


          // 實(shí)例化對象
          public Object newInstance(final int n, final Object[] array) throws InvocationTargetException {
          switch (n) {
          case 0: {
          // 此處總結(jié)通過 new 進(jìn)行了實(shí)例化
          return new CglibService();
          }
          }
          throw new IllegalArgumentException("Cannot find matching method/constructor");
          }
          復(fù)制代碼

          enchance 對象

          之前了解到 , cglib 通過重寫字節(jié)碼生成主類達(dá)到代理的目的 , 這里來看一下 , 原方法被改寫成什么樣了

          final void CGLIB$run$0() {
          super.run();
          }

          public final void run() {
          MethodInterceptor cglib$CALLBACK_2;
          MethodInterceptor cglib$CALLBACK_0;
          if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
          CGLIB$BIND_CALLBACKS(this);
          cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
          }
          if (cglib$CALLBACK_0 != null) {
          // 調(diào)用攔截器對象
          cglib$CALLBACK_2.intercept((Object)this, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$run$0$Method, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$emptyArgs, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$run$0$Proxy);
          return;
          }
          // 沒有攔截器對象 , 則直接調(diào)用
          super.run();
          }


          // 實(shí)際被調(diào)用的攔截器
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

          // 這里會(huì)調(diào)用關(guān)聯(lián)類
          // 最終通過 super.run 調(diào)用
          Object result = proxy.invokeSuper(obj, args);

          return result;
          }


          復(fù)制代碼

          此處也可以看到映射關(guān)系

          總結(jié)


          作者:AntBlack
          鏈接:https://juejin.cn/post/7015855422049353741
          來源:稀土掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。



          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜夜爽久久精品91 | 999无码| 日韩av一卡电影在线观看 | 人人草视频在线播放 | 五月花婷婷 |