1. 程式人生 > >Sprig AOP原理及原始碼解析

Sprig AOP原理及原始碼解析

      在介紹AOP之前,想必很多人都聽說AOP是基於動態代理和反射來實現的,那麼在看AOP之前,你需要弄懂什麼是動態代理和反射及它們又是如何實現的。

想了解JDK的動態代理及反射的實現和原始碼分析,請參見下面三篇文章

JDK的動態代理原始碼分析之一 (http://blog.csdn.net/weililansehudiefei/article/details/73655925)

JDK的動態代理原始碼分析之二(http://blog.csdn.net/weililansehudiefei/article/details/73656923

)

Java反射機制 (http://blog.csdn.net/weililansehudiefei/article/details/70194940)

那麼接下里進入AOP的環節。

AOP即面向切面程式設計,剛學AOP的時候,單是各種AOP的概念都搞的有點懵,什麼切面,切點,通知,織入、連線點、目標物件。。。。AOP的原理都沒看呢,這些詞語的意思就已經上人不想看了。本人將在實現AOP的時候,講解我理解的這些AOP的術語,對應的AOP的程式碼和動作。

本文將先先從程式碼實現AOP入手,然後分析AOP的底層程式碼及其原理。

後期會把所有的工程實現程式碼,放在我的GitHub上,到時候我會更新文章。

一、AOP的Demo

如果我們物件的繼承關係看成縱向關係,就像一棵樹,多個不同類的多個繼承關係就相當於有一排的樹。AOP的好處就在於,你想對這些樹進行相同的操作,這個時候,不用縱向的為每個樹定義操作方法,你只需要橫向的一刀切,給他們給個共有的操作方法。

Spring的AOP是支援JDK的動態代理和Cglib的動態代理的。JDK的動態代理是針對介面的,而Cglib是針對類的。本文針對JDK的動態代理。

首先定義一個介面:起名字時候特意給這個介面名,帶上了Interface,這樣後面會更引人注意一些。介面很簡單,裡面一個抽象方法eat()

package com.weili.cn;

/**
* Created by zsqweilai on 17/6/27.
*/
public interface AnimalInterface {
   public abstract void eat();
}

實現類:作為一個吃貨,實現類裡面當然得列印 chi  chi  chi。撐死我吧!!!
這個實現類裡面,只有一個方法,這個方法就是AOP的切點。雖然切點這個概念本身並不一定是Method,但在Spring中,所有的切點都是Method。我們增強的是方法。

package com.weili.cn;

/**
* Created by zsqweilai  on 17/6/27.
*/
public class Animal implements AnimalInterface{

public void eat() {
System.out.println("Animal類中 chi chi chi");
}
}

切面類,又稱增強類。因為我們是要用這個類的方法,來給原先的切點方法增強。切面類中,我們要去執行的方法,稱為通知。所謂織入通知,就是將切面類裡面的方法,和切點的方法進行聯絡。

 package com.weili.cn;

import org.aopalliance.intercept.Joinpoint;

/**
* Created by zsqweilai on 17/6/27.
*/
public class AdviceAnimal {
  public void animalEmpty(){
   //System.out.println("joint before "+ joinPoint.getClass().getName());
   System.out.println("我餓了");
 }

   public void animalFull(){
    System.out.println("吃飽了");
 }

   public void animalEat(){
   System.out.println("正在吃");
 }

}

接下來通過xml配置的方式,在xml檔案裡面配置AOP。
配置<aop:pointcut>的時候,通過expressi表示式,定義了com.weili.cn這個包下的所有類的所有方法 為切入點。也就是說,這個包下的所有方法,在呼叫執行的時候,會被Spring增強。具體在這裡的增強,就是在執行這些切點方法之前和之後,會分別執行animalEmpty 和 animalFull方法。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">


<bean id="animal" class="com.weili.cn.Animal"/>

<bean id="adviceAnimal" class="com.weili.cn.AdviceAnimal"/>

<aop:config>
<aop:aspect id="myaop" ref="adviceAnimal">
<aop:pointcut id="logPointcut" expression="execution(* com.weili.cn.*.*(..))" />
<aop:before method="animalEmpty" pointcut-ref="logPointcut" />
<aop:after method="animalFull" pointcut-ref="logPointcut" />
</aop:aspect>
</aop:config>
</beans>

最後就是呼叫的方法了。
我得說明一點,在我們進行spring-aop.xml解析的時候,aop還沒實現呢。在第二行getBean的時候,才真正進行aop。具體的原始碼那裡 會說明。

package com.weili.cn;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App 
{
public static void main( String[] args )
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aop.xml");
AnimalInterface animal = (AnimalInterface) ctx.getBean("animal");
animal.eat();
}
}


緊接著就是Output了。所以,我們可以看到,獲取的bean,確實是增強後的bean。那麼就趕緊看看原始碼吧。

我餓了
Animal類中 chi chi chi
吃飽了

然後回去執行invokeJoinpoint方法,

二、AOP原始碼分析

原始碼解析這塊,首先就是bean載入。之前也說了,AOP標籤也是自定義標籤,它的解析也和我們之前自定義標籤一樣,走自定義標籤的解析流程。不同的是,AOP呼叫的是AOP自己的解析器。由於在 Spring原始碼解析之二 ------ 自定義標籤的解析和註冊 中已經很詳細的描述了自定義標籤的解析流程,所以這裡我們就不再去一一看bean標籤的解析註冊。

所以AOP的原始碼分析,我們將從呼叫類裡面的第二行,ctx.getBean("animal")開始。在你除錯走到這裡的時候,在ctx中可以看到解析和註冊的bean,我們不妨先來看一下。

如下圖,這個是在第一行程式碼執行完畢後,ctx的各個屬性。可以在下圖看到,singlentonObjects中,已經存放了代理生成的animal。生層bean的過程在之前的裡面已經講的比較清楚了,這裡就不再說明。畢竟AOP嘛,我們需要知道,它是如何在我們需要執行的方法前後將我們需要執行的方法執行完成的。

ctx.getBean("animal")獲取完animal bean後,接下來呼叫eat()方法。這個時候,會進入JdkDynamicAopProxy類的invoke方法。
在這個invoke方法中,先是獲取代理類targetClass,然後根據method和targetClass獲取此方法對應的攔截器執行鏈chain。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;

Boolean var10;
try {
if(this.equalsDefined || !AopUtils.isEqualsMethod(method)) {
if(!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
Integer var18 = Integer.valueOf(this.hashCode());
return var18;
}

Object retVal;
if(!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
return retVal;
}

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

target = targetSource.getTarget();
if(target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//獲取執行鏈
if(chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}

Class<?> returnType = method.getReturnType();
if(retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
} else if(retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}

Object var13 = retVal;
return var13;
}

var10 = Boolean.valueOf(this.equals(args[0]));
} finally {
if(target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}

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

這個chain的內容如下。通過名字可以看到,一個是afterAdvice,一個是beforeAdvice。獲取chain後,構造出一個MethodInvoke方法,然後執行proceed方法。

進入proceed方法。currentInterceptorIndex的初始化值為-1.緊接著就如invoke方法。這裡的this是我們的eat方法。

public Object proceed() throws Throwable {
if(this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.invokeJoinpoint();
} else {
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if(interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)?dm.interceptor.invoke(this):this.proceed();
} else {
return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
}
}
}

在invoke方法裡,這裡的mi是我們的interface裡面的eat方法。然後執行mi的proceed()方法。

public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = (MethodInvocation)invocation.get();
invocation.set(mi);

Object var3;
try {
var3 = mi.proceed();
} finally {
invocation.set(oldInvocation);
}

return var3;
}

這個時候,會繼續回到開始時候的proceed方法。這個時候獲取到的是
interceptorOrInterceptionAdvice,也就是前面攔截器的list裡面的第二個,after的那個方法。然後繼續遞迴呼叫,會到連結串列的最後一個before方法。
最終會呼叫before裡面的方法,

public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}

然後回去執行invokeJoinpoint方法,

public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable {
   try {
   ReflectionUtils.makeAccessible(method);
   return method.invoke(target, args);
   } catch (InvocationTargetException var4) {
      throw var4.getTargetException();
   } catch (IllegalArgumentException var5) {
      throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target ["         + target + "]", var5);
  } catch (IllegalAccessException var6) {
      throw new AopInvocationException("Could not access method [" + method + "]", var6);
   }
  }
}

最後執行after方法。