Java反射機制
簡介
通過反射API可以獲取程序在運行時刻的內部結構。反射API中提供的動態代理可以原生實現AOP中的方法攔截功能。通過反射獲取到的Java類內部結構後再進行運用,和直接運用這個類效果相同,但額外的提供了運行時刻的靈活性。反射的最大一個弊端是性能比較差。相同的操作,用反射API所需的時間大概比直接的使用要慢一兩個數量級。可以考慮在適當的時機來使用反射API。
基本用法
Java反射機制主要有兩個作用。第一個主要作用是獲取程序再運行時刻的內部結構。只需要少量的代碼就能便利出一個Java類的內部結構來,其中包括構造方法、聲明的域和定義的方法等。第二個主要的作用是在運行時刻對一個Java類進行操作。可以動態的創建一個Java類的對象,獲取某個域的值以及調用某個方法。在Java源代碼中編寫的對類和對象的操作,都能通過反射來實現。
例如現在有一個簡單的Java類:
class MyClass {
public int count;
public MyClass(int start) {
count = start;
}
public void increase(int step) {
count += step;
}
}
通過一般的做法和反射API來操作類及其對象都非常的簡單。
MyClass myClass = new MyClass(0);
myClass.increase(2);
System.out.println("Normal -> " + myClass.count);
try {
Counstructor constructor = MyClass.class.getConstructor(int.class); //獲取構造方法
MyClass myClassReflect = constructor.newInstance(10); //創建對象
Method method = MyClass.class.getMethod("increase", int.class); //獲取方法
Field field = MyClass.class.getField("count"); //獲取域,也就是變量
System.out.println("Reflect -> " + field.getInt(myClassReflect)); //獲取域的值
} catch (Exception e) {
e.printStackTrace();
}
上面的例子中用到了四個反射的API:getConstructor()、newInstance()、getMethod()以及getField()。其中值得說明的是getMethod方法的一些注意事項。看下面的例子:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Pet {
private int age;
private String name;
public void eat() {
System.out.println("eat.....");
}
private void sound() {
System.out.println("sound.....");
}
}
class Dogge extends Pet {
private String gender;
public void run() {
System.out.println("run.....");
}
@Override
public void eat() {
System.out.println("eat meat.....");
}
private void bark() {
System.out.println("woo.....");
}
public Dogge() {
}
public Dogge(String gender) {
this.gender = gender;
}
}
public class JavaTest{
public static void main(String[] args) throws
InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
SecurityException,
NoSuchMethodException {
Class<?> c1 = Dogge.class;
Constructor<?>[] c = c1.getConstructors();
Dogge d1 = (Dogge) c[1].newInstance("heheda");
/**
* eat方法是繼承類重寫之後的public方法
* 可以通過reflect調用。
*/
Method eat = c1.getMethod("eat") ; //
eat.invoke(d1);
/**
* sound方法是子類的private方法,通過普通的訪問渠道調用不了
* 但是通過reflect渠道(像這裏直接reflect是不行的)能夠調用,在下一個例子中將講到。
*/
//Method sound = c1.getDeclaredMethod("sound") ;
//sound.invoke(d1);
/**
* bark方法在繼承類中是private的
* 這裏如果用getMethod()方法來reflect該方法,是無法獲取到的,原因在後面
* 注意要用到setAccessible方法,因爲是private方法,需要賦予權限。
*/
Method bark = c1.getDeclaredMethod("bark") ;
bark.setAccessible(true);
bark.invoke(d1);
/**
* run方法在繼承類中,是public方法
* 直接reflect即可
*/
Method run = c1.getMethod("run") ;
run.invoke(d1);
}
}
程序結果爲:
eat meat.....
woo.....
run.....
上面的例子說明了通過普通的reflect能夠調用到子類public,父類public,private(注意這個要設置響應的權限setAccessible(true))。下面就說明下爲什麼父類的private方法不能直接getMethod方法獲得,而是要通過getDeclaredMethod()方法去反射得到,先來看看官方文檔中對getMethod()的介紹,:
/**
* Returns a Method object that reflects the specified public
* member method of the class or interface represented by this
* Class object. The name parameter is a
* String specifying the simple name of the desired method. The
* parameterTypes parameter is an array of Class
* objects that identify the method's formal parameter types, in declared
* order. If parameterTypes is null, it is
* treated as if it were an empty array.
*
* Let C be the class represented by this object:
* C is searched for any matching methods. If no matching
* method is found, the algorithm of step 1 is invoked recursively on
* the superclass of C.
* If no method was found in step 1 above, the superinterfaces of C
* are searched for a matching method. If any such method is found, it
* is reflected.
*
*
* To find a matching method in a class C: If C declares exactly one
* public method with the specified name and exactly the same formal
* parameter types, that is the method reflected. If more than one such
* method is found in C, and one of these methods has a return type that is
* more specific than any of the others, that method is reflected;
* otherwise one of the methods is chosen arbitrarily.
*
*/
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// be very careful not to change the stack depth of this
// checkMemberAccess call for security reasons
// see java.lang.SecurityManager.checkMemberAccess
checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
兩種方法在這一行出現了分歧
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
此處直接調用了native方法getMethod0方法,繼續追蹤代碼就能發現:
private Method getMethod0(String name, Class[] parameterTypes) {
// Note: the intent is that the search algorithm this routine
// uses be equivalent to the ordering imposed by
// privateGetPublicMethods(). It fetches only the declared
// public methods for each class, however, to reduce the
// number of Method objects which have to be created for the
// common case where the method being requested is declared in
// the class which is being queried.
Method res = null;
// Search declared public methods
關鍵地方出現了,和下面的getDeclaredMethod方法可以進行對比,問題出現在該方法第一個參數的不同,此處是true,getDeclaredMethod方法此處的參數爲false.
if ((res = searchMethods(privateGetDeclaredMethods(true),
name,
parameterTypes)) != null) {
return res;
}
// Search superclass's methods
if (!isInterface()) {
Class c = getSuperclass();
if (c != null) {
if ((res = c.getMethod0(name, parameterTypes)) != null) {
return res;
}
}
}
// Search superinterfaces' methods
Class[] interfaces = getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class c = interfaces[i];
if ((res = c.getMethod0(name, parameterTypes)) != null) {
return res;
}
}
// Not found
return null;
}
下面是對getDeclaredMethod()的介紹:
/**
* Returns a Method object that reflects the specified
* declared method of the class or interface represented by this
* Class object. The name parameter is a
* String that specifies the simple name of the desired
* method, and the parameterTypes parameter is an array of
* Class objects that identify the method's formal parameter
* types, in declared order. If more than one method with the same
* parameter types is declared in a class, and one of these methods has a
* return type that is more specific than any of the others, that method is
* returned; otherwise one of the methods is chosen arbitrarily.
*
*/
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// be very careful not to change the stack depth of this
// checkMemberAccess call for security reasons
// see java.lang.SecurityManager.checkMemberAccess
checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
兩種方法在這一行出現了分歧,這裏調用的是privateGetDeclaredMethods(false),因此我們需要進一步去調查該方法。
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
進入了privateGetDeclaredMethods()方法的定義處,其中的代碼如下:
// Returns an array of "root" methods. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
// 接受傳入參數的名稱爲publicOnly,因此能明白了爲什麼getMethod()方法無法反射出類中的private方法了。
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res = null;
if (useCaches) {
clearCachesOnClassRedefinition();
if (publicOnly) {
//這個if裏面的代碼就是getMethod()方法調用時所執行的代碼,將所有的public方法都返回了
if (declaredPublicMethods != null) {
res = (Method[]) declaredPublicMethods.get();
}
} else {
//else裏面的方法即在調用getDeclaredMethod方法時所執行的。返回了所有定義的方法。
if (declaredMethods != null) {
res = (Method[]) declaredMethods.get();
}
}
if (res != null) return res;
}
// No cached value available; request value from VM
res = getDeclaredMethods0(publicOnly);
if (useCaches) {
if (publicOnly) {
declaredPublicMethods = new SoftReference(res);
} else {
declaredMethods = new SoftReference(res);
}
}
return res;
}
看到這裏,就應該差不多能弄明白了getMethod()和getDeclaredMethod()方法之間的區別。
由於數組的特殊性,Array類提供了一系列的靜態方法用來創建數組和對其中元素進行操作和訪問。例如如下的操作:
Object array = Array.newInstance(String.class,10); //等價 new String[10]
Array.set(array,0,"Hello");
Array.set(array, 1, "world");
System.out.println(Array.get(array, 0));
使用Java反射API的時候可以繞過Java默認的訪問控制檢查,比如可以直接獲取到對象的私有域的值或是調用私有方法。只需要在獲取到Constructor、Field和Method類的對象之後,調用setAccessible方法並設爲true即可。有了這種機制,就可以很方便的在運行時刻獲取到程序的內部狀態。