【Spring】AOP的代理默認是Jdk還是Cglib?


菜瓜:你是怎么理解AOP的,經常聽說它是動態代理實現的,那它默認是jdk還是cglib的實現

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

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

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

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

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

水稻:回到第二個問題,是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
    // 注解里面有個屬性proxyTargetClass缺省值是false 表示不使用cglib,也就是默認使用jdk動態代理,后面有代碼 @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 結束了。。。

菜瓜:這個自定義注解是怎樣和動態代理掛上鈎的

水稻:這個注解它看上去是從OperatorAspect#around()方法中調到了delete方法,就像代理類Proxy執行invoke方法一樣。接下來我們從Spring的角度來分析其執行過程。來理一下思路。

  • 如果使用代理模式,那最合適創建代理的地方是在哪?
  • 切面類如何被封裝到Proxy中的?
  • 最后執行調用的邏輯是怎樣?

菜瓜:go on ....

水稻:不知道你是否還記得我們之前有聊到過bean創建完畢后會調用一些PostProcessor對其進一步操作,講道理代理類也非常適合在此處操作,此時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);
              // ②如果符合切面 創建代理,持有被代理對象的引用,還有Advisor
                  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;
          }
  • 從①進去 - (標星提醒)這里需要用到AOP啟動注解@EnableAspectJAutoProxy注入類AnnotationAwareAspectJAutoProxyCreator
    • protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        /**
               * 找到所有的切面類 事務 等
               * 開啟@EnableAspectJAutoProxy注解會調用@AspectJ的收集類 {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator}
               * 調用Advisor接口的收集類 {@link BeanFactoryAdvisorRetrievalHelper} 譬如TransactionInterceptor的持有類BeanFactoryTransactionAttributeSourceAdvisor
               */
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        // 匹配beanClass對應的Advisor
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
          eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
      }
      
      
      @Override
      protected List<Advisor> findCandidateAdvisors() {
        // Add all the Spring advisors found according to superclass rules.
        List<Advisor> advisors = super.findCandidateAdvisors();
        // Build Advisors for all AspectJ aspects in the bean factory.
        if (this.aspectJAdvisorsBuilder != null) {
          // 注解@AspectJ類搜集入口
          advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }
        return advisors;
      }
      
      public List<Advisor> buildAspectJAdvisors() {
        List<String> aspectNames = this.aspectBeanNames;
      
        if (aspectNames == null) {
          synchronized (this) {
            ...
              for (String beanName : beanNames) {
                ...
                  Class<?> beanType = this.beanFactory.getType(beanName);
                if (beanType == null) {
                  continue;
                }
                // 判斷是否是@AspectJ修飾的
                if (this.advisorFactory.isAspect(beanType)) {
                  aspectNames.add(beanName);
                  AspectMetadata amd = new AspectMetadata(beanType, beanName);
                  //收集
                  if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                    MetadataAwareAspectInstanceFactory factory =
                      new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                    // 獲取@Aspect的類
                    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                    ...
                      advisors.addAll(classAdvisors);
                  }
                  ...
                    return advisors;
                }
  • 從②進去createProxy方法 -> DefaultAopProxyFactory#createAopProxy
    • @Override
      public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
      // isProxyTargetClass 呼應開始的默認配置false 創建Jdk動態代理 - 第一個參數默認為false
      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 {
      // 默認情況走這里 - 注意SpringBoot自動注入改了proxyTargetClass參數,默認是Cglib代理
      return new JdkDynamicAopProxy(config); } }

水稻:這段代碼划重點

  1. 默認生成的代理是Jdk。PS:(SpringBoot自動裝配修改了參數,默認是Cglib)
  2. 最后被放入容器(一級緩存)的對象是代理對象
  3. AOP啟動注解的功能在這里被調用,事務啟動注解同樣也是
  4. 代理對象持有被代理對象的引用,實際代碼執行時會調到增強類的Invoke代碼

菜瓜:那動態代理的代碼具體是怎么調用的

水稻:由於demo里面被注解對象不是接口調用,這里我們就展示一個Cglib代理的調用過程

  • CglibAopProxy$DynamicAdvisedInterceptor#intercept
    • @Nullable
      public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;
        Object target = null;
        TargetSource targetSource = this.advised.getTargetSource();
        try {                
          ...
            // demo中此處是ItemOcController實例
          target = targetSource.getTarget();
          Class<?> targetClass = (target != null ? target.getClass() : null);
          // 獲取攔截器調用鏈 - 此處包含是Advisor中的advice就是我們定義的注解中的Around方法
          List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
          Object retVal;
          if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            ...
          }
          else {
            // proceed調用到around方法
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
          }
          retVal = processReturnType(proxy, target, method, retVal);
          return retVal;
        }
        ...
      } 

菜瓜:終於等到你,還好我沒放棄。。。

水稻:是,這里還有一個JDK動態代理調用栗子

菜瓜:停。。。今天飽了,我下去自己斷點走一趟再熟悉熟悉

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

 

總結:

  • Spring默認配置創建的代理是JDK動態代理,SpringBoot默認是Cglib,可自行百度修改配置
  • AOP提供了在不侵入代碼的前提下動態增強目標對象的途徑,讓OOP更加專注於實現自己的邏輯
  • 而Spring的實現還是老套路,利用PostProcessor在類初始化完成之后替需要的bean創建代理對象
  • 配合@Configuration使用的@EnableAspectJAutoProxy注解在啟動時會注入其@Import類並執行注冊邏輯,后續在找切面時會掃描解析@Aspect注解的類


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM