手把手教你用Java實現AOP

ImportNew - elviskang發表於2015-03-29

介紹

眾所周知,AOP(面向切面程式設計)是Spring框架的特色功能之一。通過設定橫切關注點(cross cutting concerns),AOP提供了極高的擴充套件性。那AOP在Spring中是怎樣運作的呢?當你只能使用core java,卻需要AOP技術時,這個問題的解答變得極為關鍵。不僅如此,在高階技術崗位的面試中,此類問題也常作為考題出現。這不,我的朋友最近參加了一個面試,就被問到了這樣一個棘手的問題——如何在不使用Spring及相關庫,只用core Java的條件下實現AOP。因此,我將在本文中提供一份大綱,幫助大家瞭解如何只用core Java實現一個AOP(當然啦,這種AOP在功能上有一定的侷限性)。注意,本文不是一篇有關Spring AOP與Java AOP的對比研究,而是有關在core Java中藉助固有的設計模式實現AOP的教程。

想必讀者已經知道AOP是什麼,也知道在Spring框架中如何使用它,因此本文只著眼於如何在不用Spring的前提下實現AOP。首先,我們得知道,Spring是藉助了JDK proxy和CGlib兩種技術實現AOP的。JDK dynamic proxy提供了一種靈活的方式來hook一個方法並執行指定的操作,但執行操作時得有一個限制條件:必須先提供一個相關的介面以及該介面的實現類。實踐出真知,讓我們透過一個案例來理解這句吧!現在有一個計算器程式,用於完成一些數學運算。讓我們來考慮下除法功能,此時的問題是:如果core framework 已經具備了一份實現除法的程式碼,我們能否在程式碼執行時劫持(highjack)它並執行額外的校驗呢?答案是肯定的,我將用下面提供的程式碼片段來證明這點。首先來看基礎介面的程式碼:

 public interface Calculator {
    public int calculate( int a , int b);
}

該介面實現類的程式碼如下:

 public class CalculatorImpl implements Calculator {
    @Override
    public int calculate(int a, int b) {
        return a/b;
    }
}

假設我們既不能修該上面的程式碼,也不能對核心庫進行任何改動,怎樣才能完美地實現校驗功能呢?不如試下JDK dynamic proxy的功能吧。

public class SomeHandler implements InvocationHandler {

// Code omitted for simplicity…..

    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// Your complex business validation and logic
        Object result = method.invoke(targetObject ,params);
        return result;
    }

}

讓我們通過測試類來看看由JDK dynamic proxy實現的校驗功能的效果如何。

public static void main(String[] args) {
        CalculatorImpl calcImpl = new CalculatorImpl();
        Calculator proxied = (Calculator)ProxyFactory.getProxy (Calculator.class, calcImpl, 
                new SomeHandler(calcImpl));
        int result = proxied.calculate(20, 10);
        System.out.println("FInal Result :::" + result);
    }

從結果可以看出,簡單地實現功能強大的InvocationHandler介面,我們便能得到一個hooking implementation。按照JDK文件的描述,InvocationHandler介面是藉助一個代理例項(proxy instance)來處理一個方法呼叫的。

現在我們已經知道,InvocationHandler的invoke()方法能夠幫助我們解決問題。那麼再來解決一個新問題——怎樣才能在方法執行的前後執行操作呢?說的更具體一些,我們能通過新增多個aop(before、after、around)來hook一個方法嗎(譯註:原文為add multiple aops,但我認為Handler是充當Aspect的角色)?答案同樣是肯定的。按照以下的步驟建立一個精簡的程式碼模板便能滿足這樣的需求:

  1. 建立一個抽象類,用於將aop應用於目標物件上。
  2. 建立名為BeforeHandler 和 AfterHandler的兩個aop。前者在方法執行之前工作,而後者則在方法執行結束後工作。
  3. 建立一個代理類,使所有的aop handler和目標物件只需作為引數傳入,就能建立一個hook。
  4. 加入你自己的業務邏輯或者橫切關注點。
  5. 最後,通過傳入相關的引數建立代理物件(proxy object)。

技術實現概要

(譯註:此處是核心程式碼片段,如果想執行該例項,需進入下方提供的連結下載完整程式碼)

建立一個handler的抽象類:

public abstract class AbstractHandler implements InvocationHandler {

	private Object targetObject;

	public void setTargetObject(Object targetObject) {
		this.targetObject = targetObject;
	}
}

建立名為BeforeHandler和AfterHandler的兩個易擴充套件的handler抽象類:

public abstract class BeforeHandler extends AbstractHandler {
	public abstract void handleBefore(Object proxy, Method method, Object[] args);
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		handleBefore(proxy, method, args);
		return method.invoke(getTargetObject(), args);
	}
}
public abstract class AfterHandler extends AbstractHandler {
	public abstract void handleAfter(Object proxy, Method method, Object[] args);
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = method.invoke(getTargetObject(), args);
		handleAfter(proxy, method, args);
		return result;
	}
}

建立Proxy的工廠類:

public class ProxyFactory {

	public static Object getProxy(Object targetObject,
			List handlers) {
			//Code to get the proxy
			return proxyObject;
		} else {
			return targetObject;
		}
	}
}

以下為測試程式碼:

CalculatorImpl calcImpl = new CalculatorImpl();
BeforeHandler before = new BeforeHandlerImpl();
AfterHandler after = new AfterHandlerImpl();
List<AbstractHandler> handlers = new ArrayList<AbstractHandler>();
handlers.add(before);
handlers.add(after);
Calculator proxy = (Calculator) ProxyFactory.getProxy(calcImpl,
				handlers);
int result = proxy.calculate(20, 10);

配置

以上的程式碼片段簡明扼要地解釋了AOP在結構上的實現(structural implementation)。當然,如果能通過實際的測試將其運用到現實中去,那就再好不過了。讀者可在下面的連結中獲取完整的工程檔案,並在Java編輯器中配置它們,最後通過其中的測試類來檢驗效果。

總結

希望這篇簡短的有關AOP文章能夠幫助到大家。需說明的是,本文只實現了before和after兩種aop,而另外兩種,即“Around”和“Throw”,則希望讀者自行完成。

相關文章