探討代理模式與Java反射機制的應用

醉面韋陀發表於2010-04-01

 代理模式,相信大多數人都非常熟悉,常見的實現方式是通過公共介面的方式,讓我們的目標類和代理類實現同一介面,在代理類中呼叫目標類物件的方法。通過介面的方式,有個不好的地方,就是對每個目標類都要寫一對與之相對應的介面和代理類,如果業務類很多,就是非常繁鎖的工作了。

 

而加入反射機制的代理模式,可實現一個公共的代理類,省去我們不少功夫。Java的java.lang.reflect包及其子包中提供了Class、Method、Annotation等有用的類。下面,寫個方法代理的類MethodProxy,實現動態地呼叫物件的方法。

 

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
 * 方法代理類
 * @author rongxinhua
 *
 */
public class MethodProxy {
	
	private Class clazz;	//物件所屬的類
	private Object target;	//目標物件
	private Method method;	//目標方法
	private Object[] params;	//引數陣列
	
	@SuppressWarnings("unchecked")
	public MethodProxy(Object target, String methodName, Object ... params) {
		rebindTarget(target, methodName, params);	//設定目標物件與方法
	}
	
	/**
	 * 重新設定目標物件與方法
	 * @param target
	 * @param methodName
	 * @param params
	 */
	public void rebindTarget(Object target, String methodName, Object ... params) {
		this.target = target;
		this.clazz = target.getClass();
		rebindMethod(methodName, params);	//設定目標方法
	}
	
	/**
	 * 重新設定目標方法
	 * @param methodName
	 * @param params
	 */
	public void rebindMethod(String methodName, Object ...params) {
		this.params = params;
		int paramLength = params.length;
		Class[] paramTypes = new Class[paramLength];
		for(int i = 0 ; i < paramLength ; i ++ ) {
			paramTypes[i] = params[i].getClass();
		}
		try {
			this.method = clazz.getMethod(methodName, paramTypes);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 動態呼叫已繫結的方法
	 */
	public void doMethod() {
		try {
			this.method.invoke(target, params);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

}

 

這樣就可以實現動態地呼叫某個物件的某個方法了,寫個測試程式碼如下:

public class Manager {
	
	public void say() {
		System.out.println("Nobody say nothing");
	}
	
	public void love(String boy, String girl) {
		System.out.println(boy + " love " + girl);
	}
	
}

 

 我們通過代理類來呼叫Manager類中的say()和love()方法,測試程式碼如下:

		Manager man = new Manager();	//目標物件
		MethodProxy proxy = new MethodProxy(man, "say");	//方法代理物件
		proxy.doMethod();	//呼叫被代理的方法
		proxy.rebindMethod("love", "Tom", "Marry");	//重新繫結方法
		proxy.doMethod();	//呼叫被代理的方法

 

這樣就實現了動態代理呼叫物件的方法,上面程式碼輸出結果就不貼出來了。如果要設定前置通知和後置通知等功能,也很容易實現,只需在“proxy.doMethod()”程式碼處的前面和後面設定即行。


擴充套件應用:我們在上面的MethodProxy類中加入以下方法:

	/**
	 * 獲取方法上的註解
	 * @param anClazz 註解類
	 * @return
	 */
	public Annotation getAnnotation(Class anClazz) {
		return this.method.getAnnotation(anClazz);
	}

 

這個方法用來讀取方法上的註解(Annotation),有什麼用呢?我們寫一個註解來測試下。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Low {
	int boyAge();	//男孩法定的談戀愛年齡
	int girlAge();	//女孩法定的談戀愛年齡
}

我們要引進Annotation相關的類:

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

 

我們另外寫一個測試用的業務類:

public class LoveManager {
	
	@Low(boyAge=12, girlAge=10)
	public void beAbleToLove(Person boy, Person girl) {
		System.out.println(boy.getName() + " is able to love " + girl.getName());
	}
	
}

public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	//getter方法略
}

 

接寫上例中的proxy物件測試程式碼:

		LoveManager loveManager = new LoveManager();
		Person boy = new Person("Tom", 13);
		Person girl = new Person("Marry", 10);
		proxy.rebindTarget(loveManager, "beAbleToLove", boy, girl);	//重新繫結物件和方法
		Low low = (Low)proxy.getAnnotation(Low.class);
		if(boy.getAge() < low.boyAge()) {
			System.out.println(boy.getName() + "還不到法定年齡,不能談戀愛!");
		} else if(girl.getAge() < low.girlAge()) {
			System.out.println(girl.getName() + "還不到法定年齡,不能談戀愛!");
		} else {
			proxy.doMethod();
		}

 

根據boy和girl的年齡大小,會相應地輸出下列之一:

 

Tom還不到法定年齡,不能談戀愛!

Marry還不到法定年齡,不能談戀愛!

Tom is able to love Marry

 

這就實現了,通過Java的反射來讀取Annotation的值,並根據Annotation的值,來處理業務資料有效性的判斷,或者面向切面動態地注入物件,或者作日誌、攔截器等等。這種用法在所多框架中都常常看到, 我們在開發自己的Java元件時,不妨也採用一下吧!

相關文章