要說知道AOP這個詞卻是好久好久之前了,可是直到今天我也不敢說很是的理解它,其中的各類概念即抽象又太拗口。
在幾回面試中都被問及AOP,可是真的沒有答上來,或者都在面上,這給面試官的感受就是java基礎不行。可見這仍是挺重要的一個概念。java
在看工做中也遇到了相關的問題,在RPC的一種實現機制裏應用了AOP,結果各類類一直繞來繞去看着頭都大了,這也就是沒有對動態代理和aop有了解致使的。面試
因此要好好的去掌握它,不然吃虧的仍是本身。spring
面向側面的程序設計(aspect-oriented programming,AOP,又譯做面向方面的程序設計、觀點導向編程、剖面導向程序設計)是計算機科學中的一個術語,指一種程序設計範型。該範型以一種稱爲側面(aspect,又譯做方面)的語言構造爲基礎,側面是一種新的模塊化機制,用來描述分散在對象、類或函數中的橫切關注點(crosscutting concern)。——維基百科編程
不知道看完這段話你能理解AOP是個啥嗎?並不能,最多知道AOP是一種規範。因此說要理解AOP最重要仍是要從具體的實現入手,才能真正明白AOP究竟是幹啥子的。app
其實就是一種代碼加強方式,能夠實現動態的代理,在運行期完成;也有一些是靜態的爲對象加強,在編繹期間完成。說的白話一點就是在對象進行代碼上的擴展加強,就是說原先可能只能跑10行代碼,加強後就能夠多跑些代碼,並且這種加強是經過織入的方式完成,而不是直接修改目標對象的代碼。ide
爲此仍是要先理解代理這個概念,能夠更好的理解AOP,那麼就從學習代理開始吧。模塊化
//建立一個接口 public interface ISay { void say(); } //接口的實現類(能夠理解爲業務類) public class SayImpl implements ISay{ @Override public void say() { System.out.print("我是5207."); } } //只說一個名字太單調了,須要多多拉票,建立一個代理類來加強一下 class StaticSayImplProxy implements ISay { private ISay target; public StaticSayImplProxy(ISay target) { this.target = target; } @Override public void say() { SayHello(); this.target.say(); ThumbUp(); } void SayHello() { System.out.print("你們好:"); } void ThumbUp() { System.out.print("但願你們多多點贊."); } } //調用代碼 public class StaticProxy { public static void main(String[] args) { ISay aop = new SayImpl(); ISay aopProxy = new StaticSayImplProxy(aop); aopProxy.say(); } }
代碼很簡單,靜態代理代碼中經過增長一個StaticSayImplProxy類來對原先的實現類進行加強。注意這裏的加強很重要。也就是本來只會說一句「我是5207.」,經過代理的類後就完整了許多。函數
可是靜態代理的缺點是,代理時必須知道其類型是,好比上面代碼中就必須知道ISay這個接口,最終才能在代理類裏才能調用:this.target.say()這樣的代碼。那麼也就是說若是還有另外的IOtherSay接口也須要用這個代理類就無法使用了。工具
因此若是能把代理集中一個類中,只要將要代理的對象給這個類就能代理是否是比較方便,代碼少寫好多。學習
辦法固然是有的,JDK提供了一種動態代理的技術,能夠動態的爲接口建立代理對象,從而實現代理模式。那麼接着上面的例子咱們須要作成動態代理代碼以下:
class DynamicProxyImpl implements InvocationHandler { private Object target; public DynamicProxyImpl(Object target) { this.target = target; } @SuppressWarnings("unchecked") public <T> T getObject() { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SayHello(); Object result = method.invoke(target, args); ThumbUp(); return result; } void SayHello() { System.out.print("你們好:"); } void ThumbUp() { System.out.println("但願你們多多點贊."); } }
這裏能夠發現DynamicProxyImpl類並無指定具體代理的接口類型,而使用Object類型。這樣就不用關心傳入給這個代理類的具體對象了。因此說這個代理類的範圍一會兒就大了許多,只要是相似的加強功能均可以用這個代理類來完成。舉個例子:
public interface IOtherSay { void applause(); } public class OtherSayImpl implements IOtherSay { @Override public void applause() { System.out.print("你們鼓掌."); } } public static void main(String[] args) { IOtherSay oSay = new OtherSayImpl(); DynamicProxyImpl oSayProxy = new DynamicProxyImpl(oSay); IOtherSay oSay2 = oSayProxy.getObject(); oSay2.applause(); }
能夠看到使用同一個代理類也能夠代理IOtherSay接口派生的對象啦。
InvocationHandler接口
另一點就是DynamicProxyImpl實現了InvocationHandler接口,這個接口是關鍵,其實應當是JDK代理對象時的一個調用處理程序,這應當是暴露給開發者的代碼加強接口啦。InvocationHandler接口只有一個invoke方法,咱們須要作的就是在這個方法中增長鬚要的加強代碼。
Proxy工具類
對於getObject方法纔是真正的代理對象生成的過程,能夠看到最終是Proxy.newProxyInstance這個方法來完成代理對象生成並返回的。那麼這裏能夠看看Proxy的設計與原理。
由於暫時只用到了newProxyInstance方法,就從它開始吧:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
方法的三個參數中要注意interfaces和h
interfaces
是指的傳入須要代理的接口列表,==這裏有一點比較重要JDK的動態代理只能代理接口哦==。
h
另一個h則是指調用處理程序,發如今上面的DynamicProxyImpl類就是一個調用處理程序的實現,這裏的關鍵就是InvocationHandler。
接着看方法體中的代碼主要是幾個過程:
這裏還有些反射相關的知識就再也不說明了,可是有一個點比較好奇,就是最終代理對象是如何經過invoke把目標對象的方法代理的呢?
篇幅有點多就引用一篇吧JDK動態代理實現原理。
其實大致意思是最終JDK會自動的爲指定的接口生成代理對象,而這個生成的代理對象就和前面手寫的動態代理方法相似,只不過生成的代理類調用的是InvocationHandler的invoke。jdk幫咱們作了自動生成的過程,這樣就能夠在運行期生成代理類。
到此,最爲重要的代理差很少說完了,這也就是AOP的奧祕所在。
瞭解了代理後再來看AOP的相關概念仍是理解不了,什麼切面、鏈接點、通知、切入點之類的,因此說仍是拋開吧,這樣要輕鬆許多。相信你們看aop的時候確定是看到了Advice這個東東吧?嗯,就從它入手吧。
在Spring中Advice下面這些類型:
嗯,都是些啥意思?Berfore Advice能夠理解爲SayHello方法,那麼ThumbUp就是After Advice,兩個加一塊兒就是Around Advice。對於Throws Advice是針對異常拋出時的加強。最後還有一個比較牛的就是Introduction Advice則是能夠對即有的對象進行增長方法,這個貌似更強大點。
從這些描述能夠總結出來和前面動態代理時有殊途同歸之處,在Spring中其實就是利用了動態代理的技術,結合AOP的概念對代碼提供了一種更友好的擴展方式。固然也能夠說換了一個角度來理解代碼。舉個例子說,但願在現有系統中監控全部action的執行時間,就能夠攔截全部的action,加一個Before Advice記錄一下進入的時間,再加一個After Advice計算一下完成的時間。這樣就不會對現有action代碼作什麼修改,很是優雅啊,這對於系統設計時會有很是有用。
爲了可以更深刻的理解Spring AOP,仍是須要更深刻的去閱讀源代碼。這裏再以DynamicProxyImpl爲例子,若是使用Spring如何實現它的功能呢?以Spring xml配置方式試一下吧。
package aop.demo; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class SayImplAroundAdvice implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable { SayHello(); Object result = invocation.proceed(); ThumbUp(); return result; } void SayHello() { System.out.print("你們好:"); } void ThumbUp() { System.out.println("但願你們多多點贊."); } }
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 聲明被代理的目標對象 --> <bean id="sayImpl" class="aop.demo.SayImpl"></bean> <!-- 聲明用於加強的攔截器對象 --> <bean id="sayImplAroundAdvice" class="aop.demo.SayImplAroundAdvice"></bean> <!-- 聲明代理對象 --> <bean id="sayProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="aop.demo.ISay"/> <!-- 這個就是被代理的接口 --> <property name="target" ref="sayImpl"/> <!-- 這個就是被代理的對象 --> <property name="interceptorNames" value="sayImplAroundAdvice"/><!-- 這個就是代理的加強器 --> </bean> </beans>
package aop.demo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { @SuppressWarnings("resource") public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); ISay say = (ISay)context.getBean("sayProxy"); say.say(); } }
最後執行一下程序輸出的結果是:
你們好:我是5207.但願你們多多點贊.
哎呀呀和前面的動態代理一致,效果達成。^_^
package aop.demo; import org.springframework.aop.framework.ProxyFactory; public class ClientCode { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(); // 建立代理工廠 proxyFactory.setTarget(new SayImpl()); // 射入目標類對象 proxyFactory.addAdvice(new SayImplAroundAdvice()); ISay say = (ISay) proxyFactory.getProxy(); say.say(); } }
寫了一天才寫到這裏,只不過仍是值得的。原來一直覺得AOP就是動態代理,沒想到本身錯了,AOP是一種規範,而動態代理只是實現AOP的一種方式而已。
接下來繼續研究spring aop,進一步學習ProxyFactoryBean和ProxyFactory。
AOP 那點事兒
Java之美[從菜鳥到高手演練]之JDK動態代理的實現及原理
JDK動態代理實現原理
Spring AOP代理詳解
注:此文章爲原創,歡迎轉載,請在文章頁面明顯位置給出此文連接! 若您以爲這篇文章還不錯請點擊下右下角的推薦,很是感謝! http://www.cnblogs.com/5207