AOP執行流程


AOP的執行過程

之前跟大家聊IOC的時候跟大家聊過它的啟動過程,同樣的AOP也有指定的執行流程,但是需要IOC作為基礎。

  • IOC容器啟動,用來存放對象
  • 進行對象的實例化和初始化操作,將生成的完成的對象存放到容器中(容器運行中的一些對象比如BeanFactoryProcesser、methodInterceptore等還有其他的很多對象)
  • 從創建好的容器中獲取需要對象
  • 調用具體的方法開始調用

說了這么多理論知識,要想知道里面的具體執行流程,還是老樣子,一步一步debug進入源碼查看流程了

首先還是需要先准備配置一個切面

@Aspect
@Component
public class LogUtil {

    @Pointcut("execution(public * com.ao.bing.demo.spring.aop..*.*(..))")
    public void pctMethod() {
    }

    @Around(value = "pctMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        System.out.println("Around advice");
        return ret;
    }

    @Before("pctMethod()")
    public void before() {
        System.out.println("Before advice");
    }

    @After(value = "pctMethod()")
    public void after() {
        System.out.println("After advice");
    }

    @AfterReturning(value = "pctMethod()")
    public void afterReturning() {
        System.out.println("AfterReturning advice");
    }

    @AfterThrowing(value = "pctMethod()")
    public void afterThrowing() {
        System.out.println("AfterThrowing advice");
    }
  
  
    // main方法測試demo
      public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AopTestDemo aopTestDemo = applicationContext.getBean(AopTestDemo.class);
        aopTestDemo.method("測試AOP");

    }
}

這里配置一個LogUtil的切面demo,五種通知都寫了一遍,同時也有一個main方法測試。准備工作做完了就正式開始debug代碼了

首先在aopTestDemo里面我們打上斷點,此時我們的過去的bean對象aopTestDemo已經是通過動態代理生成的對象了,其中這里面有很多的CALLBACK方法屬性。

那這些方法屬性是什么呢?

其實這里面就又跟Spring的攔截器有關其實就是一種設計模式觀察者模式說白了就是對對象的一種行為的監聽通過回調機制來實現通知的功能

那既然是回調方法,那就先進DynamicAdvisedInterceptor方法中

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        private final AdvisedSupport advised;          

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @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();

            Object var16;
            try {
                if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

                target = targetSource.getTarget();
                Class<?> targetClass = target != null ? target.getClass() : null;
              // 從advised中 獲取配置好的AOP的通知方法 -重點
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
              // 如果沒有配置通知方法,則直接調用target對象的調用方法
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = methodProxy.invoke(target, argsToUse);
                } else {
               // 通過CglibMethodInvocation來啟動advice通知 - 重點
                    retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
                }

                retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
                var16 = retVal;
            } finally {
                if (target != null && !targetSource.isStatic()) {
                    targetSource.releaseTarget(target);
                }

                if (setProxyContext) {
                    AopContext.setCurrentProxy(oldProxy);
                }

            }

            return var16;
        }

在DynamicAdvisedInterceptor中有一個List<Object> chain這里獲取到我們配置的通知方法

 

 從上面的截圖可以看到Spring先是把所有的通知獲取到,放在一個list集合對象中,因為此時list不為空所以就又會走到 通過CglibMethodInvocation來啟動advice通知這一步的流程中

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

 protected final Object proxy;

 @Nullable
 protected final Object target;

 protected final Method method;

 protected Object[] arguments = new Object[0];

 @Nullable
 private final Class<?> targetClass;

 /**
  * Lazily initialized map of user-specific attributes for this invocation.
  */
 @Nullable
 private Map<String, Object> userAttributes;

 /**
  * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
  * that need dynamic checks.
  */
 protected final List<?> interceptorsAndDynamicMethodMatchers;

 /**
  * Index from 0 of the current interceptor we're invoking. * -1 until we invoke: then the current interceptor. */
 private int currentInterceptorIndex = -1;

 // 省略其他的方法

 @Override
 @Nullable
 public Object proceed() throws Throwable {
  // We start with an index of -1 and increment early.
    // 從索引為-1的攔截器開始調用,並且按順序遞增,如果整個List chain中調用完畢,則開始調用target的函數
    // 具體的實現方式在AOPUtils.invokeJoinpointUsingRefection方法中,說白了就是通過反射實現目標方法
  if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
   return invokeJoinpoint();
  }
  // 獲取下一個需要執行的攔截器沿着定義好的interceptorOrInterceptionAdvice的鏈進行處理
  Object interceptorOrInterceptionAdvice =
    this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
   // Evaluate dynamic method matcher here: static part will already have
   // been evaluated and found to match.
      // 對攔截器進行動態匹配,如果和定義的pointcut匹配 則就會執行當前的這個advice
   InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
   Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
   if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
    return dm.interceptor.invoke(this);
   }
   else {
    // Dynamic matching failed.
    // Skip this interceptor and invoke the next in the chain.
    return proceed();
   }
  }
  else {
   // It's an interceptor, so we just invoke it: The pointcut will have
   // been evaluated statically before this object was constructed.
      // 普通攔截器,直接調用攔截器將當前的this(CglibMethodInvocation)作為參數保證當前實例中調用鏈的執行
   return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
  }
 }

 // 省略其他的方法
}

這里需要注意的一點 從上面的源碼currentInterceptorIndex默認值定義為-1,相當於是作為判斷當前通知鏈式是執行完成,執行完那就是直接通過反射調用目標方法,否者向下接着執行。

((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)

interceptorOrInterceptionAdvice這個方法是關鍵點,即具體的調用攔截器去執行具體的方法

public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable {
 
 @Override
 public Object invoke(MethodInvocation mi) throws Throwable {
  MethodInvocation oldInvocation = invocation.get();
  invocation.set(mi);
  try {
      // 具體執行
   return mi.proceed();
  }
  finally {
   invocation.set(oldInvocation);
  }
 }
}

不知道大家發現了問題沒有,在剛上面截圖的時候List<Object> chain中有6個通知方法,而在配置的util中只配置了5個方法,

第一個這就是ExposeInvocationInterceptor這個方法,那這個到底是啥呢

其實這里是Spring引入的又一個設計模式責任鏈設計模式。

引入這個設計模式原因就是在於,配置的通知方法這么多,那spring怎么知道去執行那一個advice呢?

所以通過ExposeInvocationInterceptor(暴露調用器的攔截器)作為第一個通知方法,來保證所以的通知方法按按這中連式方式執行下去。

至此SpringAOP的通知連式結構調用流程就開始了,重復開始循環調用。一直到List chain整個鏈全部執行完畢

當前這里面還有一些其他的邏輯需我就沒有具體細說了

比如整個鏈有沒有一種順序執行?還是說根據代碼編寫的先后順序執行?

org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#sortAdvisors

獲取到整個責任鏈之后會經過一次排序執行這個排序默認是使用拓撲排序方法,大家可以具體的去看看sortAdvisors這個方法

然后在執行每一種通知方法時,都會有對應的切面通知方法

org.springframework.aop.aspectj.AspectJAfterAdvice
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
org.springframework.aop.aspectj.AspectJAroundAdvice
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
我們配置的五種通知對應着上面的五種處理邏輯

所以整個責任鏈來說通知調用鏈路可以理解為一張圖結構:

 

 以上就是Spring通知整個執行的過程,

總結一下

Spring中的五種通知,首先是通過具Spring容器的啟動過程獲取到具體的通知,

在調用對象時,通過動態代理ASM技術,把需要執行的advice先全部放在一個chain對象的集合中,

為了保證整個鏈的調用默認會先調用ExposeInvocationInterceptor去觸發整個鏈式執行,

在執行完每一個advice后都會再次回到super的proceed方法中,執行下一個advice,

在執行不通的advice時后有對應的切面通知方法,當所有的advice執行完畢再通過反射調用目標方法

整個過程大家可以自己dubug試一下,也不是特別的麻煩!!!

AOP事務功能

AOP處理常見的配置切面處理日志等業務,還有大家也比較熟悉的那就是事務。

其實AOP的事務也是通過配置的advice的方式來執行的

 

 通知對advice的拆解來實現事務的功能。

在Spring中大家可以通過@Transactional注解來現實事務功能,看過源碼的同學肯定看過TransactionAspectSupport這個類

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
 // 省略其他代碼。。。。
 @Nullable
 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
   final InvocationCallback invocation) throws Throwable {

  // If the transaction attribute is null, the method is non-transactional.
  TransactionAttributeSource tas = getTransactionAttributeSource();
  final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

  if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
   // Standard transaction demarcation with getTransaction and commit/rollback calls.
   TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
   Object retVal = null;
   try {
    // This is an around advice: Invoke the next interceptor in the chain.
    // This will normally result in a target object being invoked.
    retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
    // target invocation exception
        // 異常回滾 重點
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
   }
   finally {
    cleanupTransactionInfo(txInfo);
   } 
      // 成功后提交 重點
 commitTransactionAfterReturning(txInfo); return retVal;
  }
 
 // 省略其他的代碼。。。。

}

因此SpringAOP的聲明事務也是通過advice實現。

AOP的事務整體來說比較簡單,說白了就是通過advice的從新組合來完成事務功能,當然也是Spring的強大之處,擴展性是真的高。

總結

為了加強理解,還是有兩個比較常見的面試題

advice的通知執行流程?

看完整個流程如果還是不理解我覺得可以自己debug走一遍加深自己的理解,文中我也做了總結。但是要自己真的理解才能不會被面試官問倒

AOP中的Transactional事務是怎么實現的?

這個問題如果理解advice的調用流程那么也就能很簡單的回答了。

參考


免責聲明!

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



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