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即可。有了这种机制,就可以很方便的在运行时刻获取到程序的内部状态。