利用Java的反射與代理實現AOP

原址:http://publish.itpub.net/j/2007-10-17/200710170923968.shtml

 

     在上一篇文章中,我們講述了利用Java的反射機制中實現Spring中的IOC,在本文中,我們將更進一步,講述用Java的反射和動態代理機制來實現Spring的AOP。
一.AOP概述
       AOP(Aspect Oriented Programing),即面向切面編程,它主要用於日誌記錄、性能統計、控制、事務處理、異常處理等方面。它的主要意圖就要將日誌記錄,性能統計,安全控制、事務處理、異常處理等等代碼從業務邏輯代碼中清楚地劃分出來。通過對這些行爲的分離,我們希望可以將它們獨立地配置到業務邏輯方法中,而要改變這些行爲的時候也不需要影響到業務邏輯方法代碼。
       下面讓我們來看一個利用AOP來實現日誌記錄的例子,在沒有使用AOP之前,我們的代碼如下面所講述。
       下面這段代碼爲業務的接口類代碼:

package org.amigo.proxy; /** * 業務邏輯類接口. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:09:53 */ public interface BusinessObj { /** * 執行業務. */ public void process(); } BusinessObj接口的某個實現類代碼如下: package org.amigo.proxy; /** * 業務邏輯對象實現類. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:11:49 */ public class BusinessObjImpl implements BusinessObj { /** * 執行業務. */ public void process() { try { System.out.println("before process"); System.out.println("執行業務邏輯"); System.out.println("after process"); } catch (Exception e) { System.err.println("發生異常:" + e.toString()); } } }
    在上例中我們可以看到,在執行業務方法前、執行業務方法後以及異常發生時的日誌記錄,我們都是通過在對應的類中寫入記錄日誌的代碼來實現的,當有這種日誌記錄需求的業務邏輯類不斷增多時,將會給我們的維護帶來很大困難,而且,在上面的例子中,日誌代碼和業務邏輯代碼混合在一起,爲日後的維護工作又抹上了一層“恐怖”色彩。
       按照AOP的思想,我們首先需要尋找一個切面,在這裏我們已經找到,即在業務邏輯執行前後以及異常發生時,進行相應的日誌記錄。我們需要將這部分日誌代碼放入一個單獨的類中,以便爲以後的修改提供方便。
       我們在截獲某個業務邏輯方法時,可以採用Java的動態代理機制來實現。在下節中我們將重點講述Java的動態代理機制。
二.Java的動態代理機制
代理模式是常用的Java設計模式。代理類主要負責爲委託類預處理消息、過濾信息、把消息轉發給委託類,以及事後處理信息等。
動態代理類不僅簡化了編程工作,而且提高了軟件系統的擴展性和可維護性。我們可以通過實現java.lang.reflect.InvocationHandler接口提供一個執行處理器,然後通過java.lang.reflect.Proxy得到一個代理對象,通過這個代理對象來執行業務邏輯方法,在業務邏輯方法被調用的同時,自動調用會執行處理器。
      
下面讓我們來創建一個日誌攔截器類LogInterceptor.java文件,該類實現java.lang.reflect.InvocationHandler接口,其內容如下所示:

package org.amigo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 日誌攔截器,用來進行日誌處理. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:31:44 */ public class LogInterceptor implements InvocationHandler { private Object delegate; /** * 構造函數,設置代理對象. */ public LogInterceptor(Object delegate){ this.delegate = delegate; } /** * 方法的調用. * @param proxy * @param method 對應的方法 * @param args 方法的參信息 * @return 返回操作結果對象 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { System.out.println("before process" + method); //調用代理對象delegate的method方法,並將args作爲參數信息傳入 result = method.invoke(delegate, args); System.out.println("after process" + method); } catch (Exception e){ System.err.println("發生異常:" + e.toString()); } return result; } /** * 測試方法. * @param args * @throws Exception */ public static void main(String[] args) throws Exception { BusinessObj obj = new BusinessObjImpl(); //創建一個日誌攔截器 LogInterceptor interceptor = new LogInterceptor(obj); //通過Proxy類的newProxyInstance(...)方法來獲得動態的代理對象 BusinessObj proxy = (BusinessObj) Proxy.newProxyInstance( BusinessObjImpl.class.getClassLoader(), BusinessObjImpl.class.getInterfaces(), interceptor); //執行動態代理對象的業務邏輯方法 proxy.process(); } }
此時還需要對BusinessObj的實現類BusinessObjImpl類進行修改,去掉其日誌記錄等內容,修改後的文件如下:

package org.amigo.proxy; /** * 業務邏輯對象實現類. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:11:49 */ public class BusinessObjImpl implements BusinessObj { /** * 執行業務. */ public void process() { System.out.println("執行業務邏輯"); } }
運行LogInterceptor類我們可以發現,它實現了前面所需要的功能,但是很好的將業務邏輯方法的代碼和日誌記錄的代碼分離開來,並且所有的業務處理對象都可以利用該類來完成日誌的記錄,防止了重複代碼的出現,增加了程序的可擴展性和可維護性,從而提高了代碼的質量。那麼Spring中的AOP的實現是怎麼樣的呢?接着讓我們來對Spring的AOP進行探討,瞭解其內部實現原理。
三.Spring中AOP的模擬實現
       在學習了Java的動態代理機制後,我們在本節中將學習Java的動態代理機制在Spring中的應用。首先我們創建一個名爲AopHandler的類,該類可生成代理對象,同時可以根據設置的前置或後置處理對象分別在方法執行前後執行一些另外的操作,該類的內容如下所示:

package org.amigo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * AOP處理器. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:13:28 */ public class AopHandler implements InvocationHandler { //需要代理的目標對象 private Object target; //方法前置顧問 Advisor beforeAdvisor; //方法後置顧問 Advisor afterAdvisor; /** * 設置代理目標對象,並生成動態代理對象. * @param target 代理目標對象 * @return 返回動態代理對象 */ public Object setObject(Object target) { //設置代理目標對象 this.target = target; //根據代理目標對象生成動態代理對象 Object obj = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); return obj; } /** * 若定義了前置處理,則在方法執行前執行前置處理, * 若定義了後置處理,則在方法調用後調用後置處理. * @param proxy 代理對象 * @param method 調用的業務方法 * @param args 方法的參數 * @return 返回結果信息 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //進行業務方法的前置處理 if (beforeAdvisor != null) { beforeAdvisor.doInAdvisor(proxy, method, args); } //執行業務方法 Object result = method.invoke(target, args); //進行業務方法的後置處理 if (afterAdvisor != null) { afterAdvisor.doInAdvisor(proxy, method, args); } //返回結果對象 return result; } /** * 設置方法的前置顧問. * @param advisor 方法的前置顧問 */ public void setBeforeAdvisor(Advisor advisor) { this.beforeAdvisor = advisor; } /** * 設置方法的後置顧問. * @param advisor 方法的後置顧問 */ public void setAfterAdvisor(Advisor advisor) { this.afterAdvisor = advisor; } }
    在上類中,前置和後置顧問對象都繼承Advisor接口,接下來讓我們來看看顧問接口類的內容,該類定義了doInAdvisor(Object proxy, Method method, Object[] args)方法,如下所示:
package org.amigo.proxy; import java.lang.reflect.Method; /** * * 顧問接口類. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:21:18 */ public interface Advisor { /** * 所做的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args); } BeforeMethodAdvisor和AfterMethodAdvisor都實現了Advisor接口,分別爲方法的前置顧問和後置顧問類。 BeforeMethodAdvisor.java文件(前置顧問類)的內容如下所示: package org.amigo.proxy; import java.lang.reflect.Method; /** * * 方法前置顧問,它完成方法的前置操作. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:19:57 */ public class BeforeMethodAdvisor implements Advisor { /** * 在方法執行前所進行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("before process " + method); } } AfterMethodAdvisor.java文件(後置顧問類)的內容如下所示: package org.amigo.proxy; import java.lang.reflect.Method; /** * * 方法的後置顧問,它完成方法的後置操作. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:20:43 */ public class AfterMethodAdvisor implements Advisor { /** * 在方法執行後所進行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("after process " + method); } }
這兩個類分別在方法執行前和方法執行後做一些額外的操作。
       對於在配置文件中對某個bean配置前置或後置處理器,我們可以在bean中增加兩個屬性aop和aopType,aop的值爲對應的前置顧問類或後置顧問類的名稱,aopType用於指明該顧問類爲前置還是後置顧問,爲before時表示爲前置處理器,爲after時表示爲後置處理器,這時候我們需要修改上一篇文章中的BeanFactory.java這個文件,添加其對aop和aopType的處理,修改後的該文件內容如下:

package org.amigo.proxy; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * bean工廠類. * @author <a href="mailto:[email protected]">AmigoXie</a> * Creation date: 2007-10-7 - 下午04:04:34 */ public class BeanFactory { private Map<String, Object> beanMap = new HashMap<String, Object>(); /** * bean工廠的初始化. * @param xml xml配置文件 */ public void init(String xml) { try { //讀取指定的配置文件 SAXReader reader = new SAXReader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream ins = classLoader.getResourceAsStream(xml); Document doc = reader.read(ins); Element root = doc.getRootElement(); Element foo; //創建AOP處理器 AopHandler aopHandler = new AopHandler(); //遍歷bean for (Iterator i = root.elementIterator("bean"); i.hasNext();) { foo = (Element) i.next(); //獲取bean的屬性id、class、aop以及aopType Attribute id = foo.attribute("id"); Attribute cls = foo.attribute("class"); Attribute aop = foo.attribute("aop"); Attribute aopType = foo.attribute("aopType"); //配置了aop和aopType屬性時,需進行攔截操作 if (aop != null && aopType != null) { //根據aop字符串獲取對應的類 Class advisorCls = Class.forName(aop.getText()); //創建該類的對象 Advisor advisor = (Advisor) advisorCls.newInstance(); //根據aopType的類型來設置前置或後置顧問 if ("before".equals(aopType.getText())) { aopHandler.setBeforeAdvisor(advisor); } else if ("after".equals(aopType.getText())) { aopHandler.setAfterAdvisor(advisor); } } //利用Java反射機制,通過class的名稱獲取Class對象 Class bean = Class.forName(cls.getText()); //獲取對應class的信息 java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean); //獲取其屬性描述 java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors(); //設置值的方法 Method mSet = null; //創建一個對象 Object obj = bean.newInstance(); //遍歷該bean的property屬性 for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) { Element foo2 = (Element) ite.next(); //獲取該property的name屬性 Attribute name = foo2.attribute("name"); String value = null; //獲取該property的子元素value的值 for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) { Element node = (Element) ite1.next(); value = node.getText(); break; } for (int k = 0; k < pd.length; k++) { if (pd[k].getName().equalsIgnoreCase(name.getText())) { mSet = pd[k].getWriteMethod(); //利用Java的反射極致調用對象的某個set方法,並將值設置進去 mSet.invoke(obj, value); } } } //爲對象增加前置或後置顧問 obj = (Object) aopHandler.setObject(obj); //將對象放入beanMap中,其中key爲id值,value爲對象 beanMap.put(id.getText(), obj); } } catch (Exception e) { System.out.println(e.toString()); } } /** * 通過bean的id獲取bean的對象. * @param beanName bean的id * @return 返回對應對象 */ public Object getBean(String beanName) { Object obj = beanMap.get(beanName); return obj; } /** * 測試方法. * @param args */ public static void main(String[] args) { BeanFactory factory = new BeanFactory(); factory.init("config.xml"); BusinessObj obj = (BusinessObj) factory.getBean("businessObj"); obj.process(); } }
    觀察此類我們可以發現,該類添加了對bean元素的aop和aopType屬性的處理。編寫完該文件後,我們還需要修改src目錄下的配置文件:config.xml文件,在該文件中添加名爲businessObj的bean,我們爲其配置了aop和aopType屬性,增加了方法的前置顧問。增加的部分爲:

<bean id="businessObj" class="org.amigo.proxy.BusinessObjImpl" aop="org.amigo.proxy.BeforeMethodAdvisor" aopType="before"/>
       此時運行BeanFactory.java這個類文件,運行結果如下:
before process public abstract void org.amigo.proxy.BusinessObj.process()
執行業務邏輯
       由運行結果可以看出,前置處理已經生效。
       本節中的例子只是實現了Spring的AOP一小部分功能,即爲某個bean添加前置或後置處理,在Spring中,考慮的比這多很多,例如,爲多個bean配置動態代理等等。但是究其根源,Spring中AOP的實現,是基於Java中無比強大的反射和動態代理機制。
 
四.總結
       本文講述了AOP的概念等信息,並詳細講解了Java的動態代理機制,接着又通過一個簡單的實例講解了Spring中AOP的模擬實現,使得讀者能夠更好地學習Java的反射和代理機制。
通過這兩篇文章,使得我們能夠更加深入地理解Java的反射和動態代理機制,同時對Spring中盛行的IOC和AOP的後臺實現原理有了更加清晰的理解,Java的反射和動態代理機制的強大功能在這兩篇文章中可見一斑。有興趣的朋友可以通過學習Spring框架的源碼來進一步的理解Java的反射和動態代理機制,從而在實際的開發工作中更好地理解它。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章