Java反射機制詳細示例及動態代理

最近學習了Java中的反射.在此做以下總結:

反射可以在程序運行過程中動態獲取類的相關信息,包括類由哪個類加載器進行加載,類中的成員變量,成員方法,訪問修飾符,返回值類型,構造方法等等;

首先要獲取類的Class對象.獲取Class對象有三種方法,此處以Student類爲例:

  1. Class cls = Class.forName("com.qcc.reflect.entity.Student");
  2. Class cls = Student.class;
  3. 有對象的時候,可以通過getClass()方法來獲取Class對象 Student stu = new Student();Class cls = stu.getClass();

第一種方法在Java框架中使用最頻繁,有了Class對象後,就可以調用Class對象的相關方法來獲取類的所有的信息;

拿獲取方法做示例如下:

現有實體類Student和Person:

package com.qcc.reflect.entity;

public class Person {

	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Person() {
		super();
		// TODO Auto-generated constructor stub
	}

	public void method1() {
		System.out.println("person類: method1()");
	}

	private int method2() {
		return 0;
	}
}

Student類繼承父類Person類

package com.qcc.reflect.entity;

public class Student extends Person {
	
	private int stuno;

	public int getStuno() {
		return stuno;
	}

	public void setStuno(int stuno) {
		this.stuno = stuno;
	}

	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}

	public String method3(){
		return "Student類的公有無參方法method3()";
	}
	
	private void method4(int age){
		System.out.println("Student類的私有帶參方法method4(int age),實際參數爲:" + age);
	}
	
}

類的Class對象的getMethods()方法

可以獲取當前類以及父類中所有的公有方法;

	@Test
	public void testGetMethods() throws Exception {
		Class cls = Class.forName("com.qcc.reflect.entity.Student");
		Method[] methods = cls.getMethods();
		for(Method method: methods) {
			System.out.println(method);
		}
	}
運行結果:

public java.lang.String com.qcc.reflect.entity.Student.method3()
public void com.qcc.reflect.entity.Student.setStuno(int)
public int com.qcc.reflect.entity.Student.getStuno()
public java.lang.String com.qcc.reflect.entity.Person.getName()
public void com.qcc.reflect.entity.Person.setName(java.lang.String)
public void com.qcc.reflect.entity.Person.method1()
public int com.qcc.reflect.entity.Person.getAge()
public void com.qcc.reflect.entity.Person.setAge(int)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

類的Class對象的getDeclaredMethods()方法

可以獲取當前類中聲明的所有方法(包括私有方法);

	/**
	 * 獲取當前類中所有聲明的方法,包括私有方法
	 * @throws Exception
	 */
	@Test
	public void testDeclaredMethods() throws Exception{
		Class cls = Class.forName("com.qcc.reflect.entity.Student");
		Method[] methods = cls.getDeclaredMethods();
		for(Method method: methods) {
			System.out.println(method);
		}
	}


運行結果如下:

public java.lang.String com.qcc.reflect.entity.Student.method3()
private void com.qcc.reflect.entity.Student.method4(int)
public void com.qcc.reflect.entity.Student.setStuno(int)
public int com.qcc.reflect.entity.Student.getStuno()

類的Class對象的getMethod()方法

獲取當前類及父類中指定的公有方法

	@Test
	public void testMethod() throws Exception{
		Class cls = Class.forName("com.qcc.reflect.entity.Student");
		Method method = cls.getMethod("method1");
                method.invoke(cls.newInstance());
               System.out.println(method);
	}
運行結果如下:

person類: method1()
public void com.qcc.reflect.entity.Person.method1()

類的Class對象的getDeclaredMethod()方法

獲取當前類中聲明的任意方法,包括私有方法.當要執行私有方法時,需要先將方法的訪問權限即method的accessible的屬性設爲true,
	@Test
	public void testDeclaredMethod() throws Exception{
		Class cls = Class.forName("com.qcc.reflect.entity.Student");
		Method method = cls.getDeclaredMethod("method4",int.class);
		method.setAccessible(true);
		method.invoke(cls.newInstance(),1);
		System.out.println(method);
	}
運行結果如下:

private void com.qcc.reflect.entity.Student.method4(int)
Student類的私有帶參方法method4(int age),實際參數爲:1

此時發現,獲取私有方法與獲取公有方法調用的方法不一樣.那有沒有一種可以獲取任意方法,不管此方法是公有還是私有,是在當前類中還是在父類中,也或者是父類的父類呢?此時就需要我們手動編寫一個工具方法.

獲取任意類的任意方法實現思路如下:

  1. 先在當前類中聲明的方法中獲取指定方法
  2. 如果沒找到,則到所有父類中公有的方法中找指定的方法
  3. 如果仍未找到,則先判斷是否已到頂級類Object【因爲Object.class.getSuperclass()爲null】
  4. 如果Object.class.getSuperclass() != null ,則將當前Class對象的父類Class對象傳進去.採用遞歸算法,調用當前getMethod本身,直到找到爲止.
  5. 如果在Object類中仍未找到,則拋出異常提示信息
實現如下:

	public Method getMethod(Class cls, String methodName, Class[] classes) throws Exception {
		Method method = null;
		try {
			method = cls.getDeclaredMethod(methodName, classes);
		} catch (NoSuchMethodException e) {
			try {
				method = cls.getMethod(methodName, classes);
			} catch (NoSuchMethodException e1) {
				if(cls.getSuperclass() != null) {
					method = getMethod(cls.getSuperclass(), methodName, classes);
				} else {
					throw new Exception("所有的父類中都找不到" + methodName + "方法");
				}
			}
		}
		return method;
	}
編寫測試代碼,測試getMethod()方法,因method1,method2,method3三個方法都沒有參數列表,因此使用循環統一測試(純屬省事.):

	@Test
	public void testGetMethod() throws Exception{
		Class cls = Class.forName("com.qcc.reflect.entity.Student");
		String methodName = null;
		for(int i=1; i<4; i++) {
			methodName = "method" + i;
			Method method = getMethod(cls, methodName, null);
			System.out.println(method);
			System.out.println("---------------------");
		}
		methodName = "method4";
		Method method = getMethod(cls, methodName,new Class[]{int.class});
		System.out.println(method);
	}
運行結果:

public void com.qcc.reflect.entity.Person.method1()
---------------------
private int com.qcc.reflect.entity.Person.method2()
---------------------
public java.lang.String com.qcc.reflect.entity.Student.method3()
---------------------
private void com.qcc.reflect.entity.Student.method4(int)
Field的獲取方式與Method相同,也分以上四類.略.

使用Java反射機制來實現動態代理

現在有一個CalcDao接口,提供加減乘除的四個簡單方法,有一個CalcDaoImpl的實現類實現了CalcDao接口,現在要求在調用實現類中的每個方法之前提示方法開始執行,執行完成後提示執行完畢,並統計方法調用的時間.
如果在每個方法中分別添加以上需求的實現,代碼重複量大,代碼耦合度高,不利於維護,使用java的動態代理可以很輕鬆的實現以上需求.
使用Java的反射機制,創建動態代理對象.讓代理對象在調用目標方法之前和之後分別做一些事情,然後動態代理對象決定是否調用以及何時來調用被代理對象的方法(可以加一些驗證,驗證不通過則不去調用目標方法,此處示例略)
CalcDao接口和CalcDaoImpl類的代碼如下:

package com.qcc.reflect.test;

/**
 * 簡單計算器類
 * 提供加.減.乘.除 運算
 * @author QianChaoChen 00002336<br>
 * @date 2016年9月30日 上午9:47:05s
 */
public interface CalcDao {
	int add(int i, int j);
	int sub(int i, int j);
	int mul(int i, int j);
	int div(int i, int j);
}
package com.qcc.reflect.test;

public class CalcDaoImpl implements CalcDao {

	@Override
	public int add(int i, int j) {
		int r = i + j;
		return r;
	}

	@Override
	public int sub(int i, int j) {
		int r = i - j;
		return r;
	}

	@Override
	public int mul(int i, int j) {
		int r = i * j;
		return r;
	}

	@Override
	public int div(int i, int j) {
		int r = i / j;
		return r;
	}

}
實現動態代理的步驟如下:

1、創建被代理的對象(接口類型)
2、通過代理類Proxy的newProxyInstance()方法創建代理對象
3、newProxyInstance()方法的三個參數:
      3.1 ClassLoader:類加載器,即代理對象使用哪個類加載器進行加載,一般和被代理對象使用相同的.
      3.2 Class類型的數組,此數組中只能放接口的Class對象.若代理對象不需要實現被代理對象已經實現的接口以外的接口,則可以使用
      3.3 InvocationHandler一般使用匿名內部類的方式,被代理對象的類型要求是final類型的.編譯器會給出提示. 

動態代理的單元測試方法如下:

	@Test
	public void testProxy(){
		//創建被代理的對象(接口類型)
		final CalcDao target = new CalcDaoImpl();
		//通過代理類Prxoy的newProxyInstance()方法創建代理對象
		CalcDao proxy = (CalcDao) Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(), new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						System.out.println("方法【" + method.getName() + "】開始執行, 參數爲" + Arrays.asList(args) + "...");
						long start = System.currentTimeMillis();
						Object result = method.invoke(target, args);
						long end = System.currentTimeMillis();
						System.out.println("方法【" + method.getName() + "】執行完成,運算結果爲:" + result + ", 用時" + (end - start) + "毫秒!");
						return result;
					}
				});
		proxy.add(10, 2);
		proxy.sub(10, 2);
		proxy.mul(10, 2);
		proxy.div(10, 2);
	}
	
}
proxy.add(10,2)是調用代理對象的add方法,它會去調用InvocationHandler的匿名內部類中的invoke方法,invoke方法中method.invoke(target,args)其實是調用被代理對象target的method方法,(此處就是反射在動態代理中最核心的部分)傳遞的參數數組是args,代理對象會在method.invoke()方法之前和之後做一些事情.

InvocationHandler接口的實現類中重寫的invoke()方法也有三個參數,
proxy:正在生成的代理對象本身;
method:正在被調用的那個方法
args:被調用的方法中實際傳遞的參數;

result是調用目標方法後的返回值,如果方法本身沒有返回值,則result爲null.
以上測試代理對象的運行結果:

方法【add】開始執行, 參數爲[10, 2]...
方法【add】執行完成,運算結果爲:12, 用時0毫秒!
方法【sub】開始執行, 參數爲[10, 2]...
方法【sub】執行完成,運算結果爲:8, 用時0毫秒!
方法【mul】開始執行, 參數爲[10, 2]...
方法【mul】執行完成,運算結果爲:20, 用時0毫秒!
方法【div】開始執行, 參數爲[10, 2]...
方法【div】執行完成,運算結果爲:5, 用時0毫秒!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章