在介紹AOP之前,想必很多人都聽說AOP是基於動態代理和反射來實現的,那麼在看AOP之前,你需要弄懂什麼是動態代理和反射及它們又是如何實現的。 想瞭解JDK的動態代理及反射的實現和源碼分析,請參見下麵三篇文章 JDK的動態代理源碼分析之一 (http://blog.csdn.net/weililan ...
在介紹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的底層代碼及其原理。
一、AOP的Demo
如果我們把對象的繼承關係看成縱向關係,就像一棵樹,多個不同類的多個繼承關係就相當於有一排的樹。AOP的好處就在於,你想對這些樹進行相同的操作時,這個時候,不用縱向的為每個樹定義操作方法,你只需要橫向的一刀切,給他們提供個共有的操作方法。
Spring的AOP是支持JDK的動態代理和Cglib的動態代理的。JDK的動態代理是針對介面的,而Cglib是針對類的。本文針對JDK的動態代理。
首先定義一個介面:起名字時候特意給這個介面名,帶上了Interface,這樣後面會更引人註意一些。介面很簡單,裡面一個抽象方法eat()
package com.weili.cn;* 動物介面,提供一個eat的抽象方法
* 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
吃飽了
二、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方法。