簡介 前段時間寫的java設計模式--代理模式,最近在看Spring Aop的時候,覺得於代理模式應該有密切的聯繫,於是決定瞭解下Spring Aop的實現原理。 說起AOP就不得不說下OOP了,OOP中引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。但是,如果我們需 ...
- 簡介
前段時間寫的java設計模式--代理模式,最近在看Spring Aop的時候,覺得於代理模式應該有密切的聯繫,於是決定瞭解下Spring Aop的實現原理。
說起AOP就不得不說下OOP了,OOP中引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。但是,如果我們需要為部分對象引入公共部分的時候,OOP就會引入大量重覆的代碼。例如:日誌功能。
AOP技術利用一種稱為“橫切”的技術,解剖封裝的對象內部,並將那些影響了多個類的公共行為封裝到一個可重用模塊,這樣就能減少系統的重覆代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。AOP把軟體系統分為兩個部分:核心關註點和橫切關註點。業務處理的主要流程是核心關註點,與之關係不大的部分是橫切關註點。橫切關註點的一個特點是,他們經常發生在核心關註點的多處,而各處都基本相似。比如許可權認證、日誌、事務處理。
- 實現原理
前面在學習代理模式的時候,瞭解到代理模式分為動態代理和靜態代理。現在我們就以代理模式為基礎先實現我們自己的AOP框架,再來研究Spring的AOP的實現原理。
先以靜態代理實現,靜態代理關鍵是在代理對象和目標對象實現共同的介面,並且代理對象持有目標對象的引用。
公共介面代碼:
1 public interface IHello { 2 /** 3 * 業務方法 4 * @param str 5 */ 6 void sayHello(String str); 7 }
目標類代碼:
1 public class Hello implements IHello{ 2 @Override 3 public void sayHello(String str) { 4 System.out.println("hello "+str); 5 } 6 7 }
代理類代碼,我們給它添加日誌記錄功能,在方法開始前後執行特定的方法,是不是和AOP特別像呢?
public class ProxyHello implements IHello{ private IHello hello; public ProxyHello(IHello hello) { super(); this.hello = hello; } @Override public void sayHello(String str) { Logger.start();//添加特定的方法 hello.sayHello(str); Logger.end(); } }
日誌類代碼:
1 public class Logger { 2 public static void start(){ 3 System.out.println(new Date()+ " say hello start..."); 4 } 5 6 public static void end(){ 7 System.out.println(new Date()+ " say hello end"); 8 } 9 }
測試代碼:
1 public class Test { 2 public static void main(String[] args) { 3 IHello hello = new ProxyHello(new Hello());//如果我們需要日誌功能,則使用代理類 4 //IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類 5 hello.sayHello("明天"); 6 } 7 }
這樣我們就實現了一個最簡單的AOP,但是這樣會存在一個問題:如果我們像Hello這樣的類很多,那麼,我們是不是要去寫很多個HelloProxy這樣的類呢。其實也是一種很麻煩的事。在jdk1.3以後,jdk跟我們提供了一個API java.lang.reflect.InvocationHandler的類, 這個類可以讓我們在JVM調用某個類的方法時動態的為些方法做些什麼事。下麵我們就來實現動態代理的實現。
動態代理實現主要是實現InvocationHandler,並且將目標對象註入到代理對象中,利用反射機制來執行目標對象的方法。
介面實現與靜態代理相同,代理類代碼:
1 public class DynaProxyHello implements InvocationHandler{ 2 3 private Object target;//目標對象 4 /** 5 * 通過反射來實例化目標對象 6 * @param object 7 * @return 8 */ 9 public Object bind(Object object){ 10 this.target = object; 11 return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); 12 } 13 14 @Override 15 public Object invoke(Object proxy, Method method, Object[] args) 16 throws Throwable { 17 Object result = null; 18 Logger.start();//添加額外的方法 19 //通過反射機制來運行目標對象的方法 20 result = method.invoke(this.target, args); 21 Logger.end(); 22 return result; 23 } 24 25 }
測試類代碼:
1 public class DynaTest { 2 public static void main(String[] args) { 3 IHello hello = (IHello) new DynaProxyHello().bind(new Hello());//如果我們需要日誌功能,則使用代理類 4 //IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類 5 hello.sayHello("明天"); 6 } 7 }
看完上面的代碼可能和Spring AOP相比有一個問題,日誌類只能在方法前後列印,但是AOP應該是可以在滿足條件就可以執行,所有是否可以將DynaPoxyHello對象和日誌操作對象(Logger)解耦呢?
看下麵代碼實現,將將DynaPoxyHello對象和日誌操作對象(Logger)解耦:
我們要在被代理對象的方法前面或者後面去加上日誌操作代碼(或者是其它操作的代碼),那麼,我們可以抽象出一個介面,這個介面里就只有兩個方法:一個是在被代理對象要執行方法之前執行的方法,我們取名為start,第二個方法就是在被代理對象執行方法之後執行的方法,我們取名為end。
Logger的介面:
1 public interface ILogger { 2 void start(Method method); 3 void end(Method method); 4 }
Logger的介面實現:
1 public class DLogger implements ILogger{ 2 @Override 3 public void start(Method method) { 4 System.out.println(new Date()+ method.getName() + " say hello start..."); 5 } 6 @Override 7 public void end(Method method) { 8 System.out.println(new Date()+ method.getName() + " say hello end"); 9 } 10 }
動態代理類:
1 public class DynaProxyHello1 implements InvocationHandler{ 2 //調用對象 3 private Object proxy; 4 //目標對象 5 private Object target; 6 7 public Object bind(Object target,Object proxy){ 8 this.target=target; 9 this.proxy=proxy; 10 return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); 11 } 12 13 14 @Override 15 public Object invoke(Object proxy, Method method, Object[] args) 16 throws Throwable { 17 Object result = null; 18 //反射得到操作者的實例 19 Class clazz = this.proxy.getClass(); 20 //反射得到操作者的Start方法 21 Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class}); 22 //反射執行start方法 23 start.invoke(this.proxy, new Object[]{this.proxy.getClass()}); 24 //執行要處理對象的原本方法 25 method.invoke(this.target, args); 26 //反射得到操作者的end方法 27 Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class}); 28 //反射執行end方法 29 end.invoke(this.proxy, new Object[]{method}); 30 return result; 31 } 32 33 }
測試代碼:
1 public class DynaTest1 { 2 public static void main(String[] args) { 3 IHello hello = (IHello) new DynaProxyHello1().bind(new Hello(),new DLogger());//如果我們需要日誌功能,則使用代理類 4 //IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類 5 hello.sayHello("明天"); 6 } 7 }
通過上面例子,可以發現通過動態代理和發射技術,已經基本實現了AOP的功能,如果我們只需要在方法執行前列印日誌,則可以不實現end()方法,這樣就可以控制列印的時機了。如果我們想讓指定的方法列印日誌,我們只需要在invoke()方法中加一個對method名字的判斷,method的名字可以寫在xml文件中,這樣我們就可以實現以配置文件進行解耦了,這樣我們就實現了一個簡單的spring aop框架。