1. 程式人生 > >理解Java 動態代理和AOP(可以自己動手寫AOP框架!)

理解Java 動態代理和AOP(可以自己動手寫AOP框架!)

說到AOP,很容易就想到 spring AOP。因為它是AOP裡最優秀的實現框架。但本文不打算討論 spring AOP,只想把如何通過動態代理方式實現AOP思想說通。當然,整明白了這個道理,理解 spring AOP 也就簡單了!

首先我覺得需特別強調一下什麼是面向介面程式設計!

用本人的意思理解,面向介面程式設計,有兩個方面的角色,一個是介面的實現者,一個是介面的使用者,而介面本質上是一種服務規範,它規定了一個規範是通過什麼方式向服務物件提供服務的,有什麼引數,有什麼約束,會有什麼異常無法處理等。對介面的實現者來說,他們只需要按介面規定的要求提供服務即可,不必關心誰會來使用介面;對介面使用者來看,他只關注介面能提供什麼功能和服務,不必關心介面是怎麼實現的。

比如 JDK 裡,有一個介面 java.util.List。它規定了一個列表可以提供的一系列服務:新增元素、移除元素、根據索引取元素、得到元素數量……。而介面有兩個實現:ArrayList 和 LinkedList。

我們編寫程式時,作為介面的使用者,可以用

List lst = new ArrayList();

然後把它當作 List 介面的一個例項即可。如果哪天不高興,把 new ArrayList(); 改為 new LinkedList(); ,對我們寫的程式來說不會有業務邏輯上的變化。

而對介面的實現者,就是sun公司(現在是Oracle公司),他們在實現 ArrayList 和 LinkedList 時,並沒有限制這兩個實現只能用到XXX系統裡,而不能用到YYY系統裡。也就是說,介面的實現者不關心介面會被什麼人使用。

Java動態代理的基本例子網上有很多,在本文也是隨便找了一篇閱讀之後略微改了一下。

首先是定義一個介面(Animate):

package com.csjl.tangram.test;

public interface Animate {

	public void printName();
	
	public String getName();
}

然後是介面的實現(Dog、Cat):

package com.csjl.tangram.test;

public class Dog implements Animate {

	@Override
	public void printName() {
		System.out.println("This is dog!");
	}

	@Override
	public String getName() {
		return "dog";
	}

}
package com.csjl.tangram.test;

public class Cat implements Animate {

	@Override
	public void printName() {
		System.out.println("This is cat!");
	}

	@Override
	public String getName() {
		return "cat";
	}

}

然後是一個代理處理類(SimpleProxy):

package com.csjl.tangram.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SimpleProxy implements InvocationHandler {

	private Object subject;
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		
		System.out.println("Begin invoke method: " + method.getName());
		Object result = method.invoke(this.subject, args);
		System.out.println("Finished invoke method: " + method.getName());
		
		return result;
	}

	public SimpleProxy(Object subject){
		this.subject = subject;
	}
}

最後是測試類(Client):

package com.csjl.tangram.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {

	
	public static void main(String[] args){
		Cat cat = new Cat();
		
		InvocationHandler handler = new SimpleProxy(cat);
		
		Animate animate = 
				(Animate)Proxy.newProxyInstance(
						Cat.class.getClassLoader(), 
						Cat.class.getInterfaces(), 
						handler);
		
		System.out.println("Animate: " + animate.getClass().getName());
		
		System.out.println(">>>>>>>>>>>>>>>>>>>>");
		animate.printName();
		System.out.println();
		System.out.println("animate.getName() = " + animate.getName());
		System.out.println(">>>>>>>>>>>>>>>>>>>>");
	}
}

最後執行 Client 類的結果是:

Animate: com.sun.proxy.$Proxy0
>>>>>>>>>>>>>>>>>>>>
Begin invoke method: printName
This is cat!
Finished invoke method: printName

Begin invoke method: getName
Finished invoke method: getName
animate.getName() = cat
>>>>>>>>>>>>>>>>>>>>

注意到第一條列印語句是:Animate: com.sun.prox.$Proxy0

這個本人沒有深入研究,網上別人的文章說這是 jvm 自動建立的類。但不管怎麼樣,它確實實現了 Animate 介面。

把上面的程式碼分為幾大塊:

1、介面定義(Animate)

2、介面實現(Cat、Dog 類)

3、得到 Animate 例項(Client.main 方法中,Proxy.newProxInstance  語句之前的程式碼全部)

4、呼叫 Animate 方法,實現業務邏輯

根據前面說的面向介面程式設計,上面的幾塊程式碼中:1是介面定義;2是介面實現;3和4是介面使用。而對方法的呼叫,是由自定義的代理類來實現的,那麼代理在具體呼叫方法前、後,以及呼叫發生異常時都可以進行一些動作。這就是Java能夠實現AOP的前提條件。在上面的程式中,只是在呼叫前後列印了一些語句,實際專案中會複雜得多。

如果使用了AOP框架,上面的第3塊程式碼中得到 Animate 例項的具體實現一般是由框架根據配置檔案動態生成,使用哪個代理也可以由配置檔案動態指定。當然在 spring AOP 中,機制會複雜得多、功能會強大得多。本文只描述對AOP基本思想的理解。

看看下面修改後的程式,應該可以對AOP思想有了比較清晰的理解

介面定義(Animate,同上,略過)

介面實現(Dog、Cat,同上,略過)

定義了抽象的代理類(AbstractProxy),定義了抽象的方法呼叫前、後的操作,具體怎麼操作由子類完成

package com.csjl.tangram.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public abstract class AbstractProxy implements InvocationHandler {

	private Object subject;
	
	public Object getSubject(){
		return subject;
	}
	
	public void setSubject(Object value){
		subject = value;
	}
	
	public abstract boolean beforeInvoke(Method method, Object[] args);
	
	public abstract Object afterInvoke(Method method, Object[] args, Object result);
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		
		if(this.beforeInvoke(method, args) == false)
			return null;
		
		Object result = method.invoke(this.subject, args);
		
		return this.afterInvoke(method, args, result);
	}
}

實現切片操作的代理類(RealProxy),實現代理類可以有多個,具體由哪個進行代理,可以由配置檔案指定

package com.csjl.tangram.test;

import java.lang.reflect.Method;

public class RealProxy extends AbstractProxy {

	@Override
	public boolean beforeInvoke(Method method, Object[] args) {
		System.out.println("呼叫方法 " + method.getName() + " 之前的操作,看我能先準備點什麼");
		return true;
	}

	@Override
	public Object afterInvoke(Method method, Object[] args, Object result) {
		System.out.println("完成對方法 " + method.getName() + " 的呼叫,看我需要做什麼收尾工作");
		return result;
	}

}

最後是測試類

package com.csjl.tangram.test;

import java.lang.reflect.Proxy;

public class Client {

	private static Animate getAnimate(){
		
		try{
			Class<?> cls = null;
			
			// 使用框架時,如何例項化animate會從配置檔案中讀取
			cls = Class.forName("com.csjl.tangram.test.Cat");
			Animate animate = (Animate)cls.newInstance();
			
			// 使用框架時,如何實現化 handler 會從配置檔案中讀取
			cls = Class.forName("com.csjl.tangram.test.RealProxy");
			RealProxy handler = (RealProxy)cls.newInstance();
			handler.setSubject(animate);
			
			Animate result = 
					(Animate)Proxy.newProxyInstance(
							animate.getClass().getClassLoader(), 
							animate.getClass().getInterfaces(),
							handler);
			
			return result;
		}catch(Exception ex){
			ex.printStackTrace();
			return null;
		}
	}
	
	
	public static void f2(){
		Animate animate = getAnimate();
		System.out.println("我要呼叫介面了");
		animate.printName();
	}
	
	public static void main(String[] args){
		f2();
	}
}

在以上測試類中的 getAnimate 方法,在實際專案中一般建議使用一個工廠類使用工廠模式建立,這裡簡化起見直接寫到 Client 類中了。

從以上的程式碼結構中我們可以看到:

1、介面的定義和傳統沒什麼兩樣

2、介面的實現和傳統沒什麼兩樣

3、介面的呼叫和傳統沒什麼兩樣,不一樣的是修改 getAnimate 方法的實現,但這不會影響到主業務邏輯

4、但通過繼承 AbstractProxy 類,可以在 Animate.printName() 和 Animate.getName() 方法的呼叫前後、異常發生時做一些邏輯處理工作。並且這些操作是可以通過配置進行動態修改的。

如果理解了Java動態代理,讀者可以自己寫一個小框架實現簡單的AOP了!其實也就是上面程式碼中註釋裡提到的那些內容!