【Spring】AOP的代理默認是Jdk還是Cglib?,【DP-動態代理】JDK&Cglib

菜瓜:你覺得AOP是啥

水稻:我覺得吧,AOP是對OOP的補充。通常情況下,OOP代碼專註功能的實現,所謂面向切面編程,大多數時候是對某一類對象的方法或者功能進行增強或者抽象

菜瓜:我看你這個理解就挺抽象的

水稻:舉個栗子!我要在滿足開閉原則的基礎下對已有功能進行擴展

  • 我現在想對很多個功能增加日誌功能,但是代碼已經打好包了,不想改。又或者有時候方法調用很慢,想定位問題
  • low一點的方法就是每個方法調用之前記錄調用開始,之後記錄調用結束

菜瓜:你說的這個low一點的方法怎麼好像是在說我???

水稻:建議看一下動態代理設計模式【DP-動態代理】JDK&Cglib,我當然知道你不會看,所以我還準備了自定義註解的栗子

  • package com.hb.merchant.config.aop;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    /**
     * @author QuCheng on 2020/6/23.
     */
    @Configuration
    @EnableAspectJAutoProxy
    public class AopConfig {
    }
    
    package com.hb.merchant.config.aop;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author QuCheng on 2020/6/23.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface OperatorLog {
    }
    
    
    package com.hb.merchant.config.aop;
    
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    
    /**
     *
     * @author QuCheng on 2020/6/23.
     */
    @Aspect
    @Component
    @Slf4j
    public class OperatorAspect {
    
        @Around("@annotation(OperatorLog)")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            //獲取要執行的方法
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            //記錄方法執行前日誌
            log.info("startLog: {} 開始了。。。" , methodSignature.getName());
            //獲取方法信息
            String[] argNames = methodSignature.getParameterNames();
            // 參數值:
            final Object[] argValues = joinPoint.getArgs();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < argNames.length; i++) {
                String value = argValues[i] == null ? "null" : argValues[i].toString();
                sb.append(argNames[i]).append("=").append(value).append(",");
            }
            String paramStr = sb.length() > 0 ? sb.toString().substring(0, sb.length() - 1) + "]" : "";
            log.info("參數信息為:[{}", paramStr);
    
            //執行方法
            Object result;
            try {
                result = joinPoint.proceed();
            } catch (Exception e) {
                log.error("errorLog", e);
                return null;
            }
    
            //記錄方法執行後日志
            log.info("endLog: {} 結束了。。。" , methodSignature.getName());
            return result;
        }
    
    }
    
    
    
    package com.hb.merchant.controller.icbc.item.oc;
    
    import com.hb.merchant.config.aop.OperatorLog;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.Assert;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author QuCheng on 2020-06-23.
     */
    @RestController
    @RequestMapping("/item")
    @Slf4j
    public class ItemOcController {
    
        @OperatorLog
        @GetMapping("/delete")
        public String delete(Long itemId) {
            Assert.notNull(itemId,"itemId不能為空");
            return "delete finished ...";
        }
    }

    // 後台打印
    startLog: delete 開始了。。。
    參數信息為:[itemId=1]
    endLog: delete 結束了。。。

菜瓜:這個自定義註解又是怎麼實現的呢?

水稻:不愧是你,沒有源碼看來是滿足不了你的好奇心了!!不知道你是否還記得我們之前有聊到過bean創建完畢後會調用一些PostProcessor對其進一步操作

菜瓜:有印象,@PostConstruct註解就是InitDestroyAnnotationBeanPostProcessor在這裏調用的,還自定義過BeanPostProcessorT對象打印輸出過bean信息

水稻:你猜Spring是怎麼操作的

菜瓜:let me try try。結合剛剛的栗子和提示,大膽猜測應該是用PostProcessor在bean創建完成之後生成代理對象。實際調用代理的invoke方法實現對被代理bean的增強

水稻:思路正確。看脈絡

  • 入口在AbstractAdvisorAutoProxyCreator#initializeBean
  • protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
            。。。
                // BeanNameAware BeanFactoryAware ...
                invokeAwareMethods(beanName, bean);
        。。。    
                // BeanPostProcessorBefore  @PostConstruct
                wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        。。。
                // initMethod InitializingBean接口
                invokeInitMethods(beanName, wrappedBean, mbd);
                。。。
            if (mbd == null || !mbd.isSynthetic()) {
                // aop
                wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
            }
            return wrappedBean;
        }
  • 從aop入口跟下去
  • protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
            。。。
            // 收集切面信息匹配被代理對象
            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方法 -> DefaultAopProxyFactory#createAopProxy
  • @Override
    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)) {
            // jdk動態代理類
             return new JdkDynamicAopProxy(config);
          }
          // cglib
          return new ObjenesisCglibAopProxy(config);
       }
       else {
          return new JdkDynamicAopProxy(config);
       }
    }
  • 此處省略了切面類搜集和匹配的過程。可以簡單理解成搜集到所有的切面類信息獲取pointcut的目錄或者註解信息,匹配當前bean是否屬於pointcut目標範圍
  • 另外我們可以看到最後返回的bean已經不是原始bean了,而是代理對象。也就是說getBean(“xxx”)返回的對象實際是代理對象,被代理對象被其成員變量直接引用

菜瓜:然後代理類中都有invoke方法,那些advice(@Around,@Before…)在invoke中找到適當時機調用對吧

水稻:是的,這裏我想結合@Transactional註解會更容易理解,你肯定用過這個註解吧,它其實。。。

菜瓜:停。。。今天獲取的知識量已經夠了,我下去自己斷點走一趟再熟悉熟悉。下次請結合Transactional註解再敲打我吧

水稻:也好,我下去再給你準備幾個栗子

 

總結:

  • AOP提供了在不侵入代碼的前提下動態增強目標對象的途徑,讓OOP更加專註於實現自己的邏輯
  • 而Spring的實現還是老套路,利用PostProcessor在類初始化完成之後替需要的bean創建代理對象
  • 這裏還有一些細節沒有照顧到,譬如說AOP解析類是什麼時候註冊到IOC容器的(偷偷告訴你從@EnableAspectJAutoProxy註解下手)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

您可能也會喜歡…