Java反射学习

Sep 5, 2016


  java反射机制听起来似乎很神奇,在自己编程的时候却很少用到,但是在框架中却频繁的使用。以前自己看过一些java反射的内容,但是也没有搞得十分透彻,今天再重新学习一遍,争取把这个问题彻底搞清楚。

一、反射——并不神奇

  运行时类型信息(RTTI)可以告诉我们运行时某个对象的类型,只要这个类在编译时已经加载。那么如果在编译阶段我们无法获取某个类的信息,却需要使用类对象怎么办呢?这看起来似乎不可思议,但是当我们处于更大规模的编程世界中时,在许多重要的情况下上面的情况就会发生。这时就需要用到 反射 啦!
  反射的名字就显得非常的贴切啦。正常情况下,需要先加载类信息,然后再产生对象,而反射是先有对象,再通过对象获取类信息。Class 类和 java.lang.reflect 类库一起对反射概念进行了支持,该类库包含 FieldMethod 、 以及 Constructor 类(每个类都实现了 Member 接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里的对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取或修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等方法以便返回表示字段、方法以及构造器对象的数组。这样,匿名对象的类信息就能够在运行时完全的确定下来,而不需要在编译的时候知道类信息。
  这样看来反射机制并没有任何神奇的地方,只是在使用一个匿名对象时,JVM会通过放射检查这个对象到底属于哪个类型。
  引用一段《java编程思想》中的代码,来看看反射是如何工作的。

import java.lang.reflect.*;
import java.util.regex.*;

public class ShowMethods {
	private static String usage =
			"usage: \n" +
	"ShowMethods qualified.class.name\n" +
					"To show all methods in class or:\n" +
	"ShowMethods qualified.class.name word\n" +
					"To search for nethods involving 'word'";

	private static Pattern p = Pattern.compile("\\w+\\.");

	public static void main(String[] args) {
		if(args.length < 1) {
			System.out.println(usage);
			System.exit(0);
		}
		int lines = 0;
		try {
			Class<?> c = Class.forName(args[0]);
			Method[] methods = c.getMethods();
			Constructor[] ctors = c.getConstructors();
			if(args.length == 1) {
				for(Method method: methods) {
					System.out.println(p.matcher(method.toString()).replaceAll(""));
				}
				for(Constructor ctor :ctors) {
					System.out.println(p.matcher(ctor.toString()).replaceAll(""));
				}
				lines = methods.length + ctors.length;
			} else {
				for(Method method: methods) {
					if(method.toString().indexOf(args[1]) != -1) {
						System.out.println(p.matcher(method.toString()).replaceAll(""));
						lines++;
					}
					System.out.println(p.matcher(method.toString()).replaceAll(""));
				}
				for(Constructor ctor: ctors) {
					if(ctor.toString().indexOf(args[1]) != -1) {
						System.out.println(p.matcher(ctor.toString()).replaceAll(""));
						lines++;
					}
					System.out.println(p.matcher(ctor.toString()).replaceAll(""));
				}
			}
		} catch (ClassNotFoundException e) {
			System.out.println("class not found " + e);
		}
	}
}

  运行上面的代码,我传入的参数为reflection.Reflection,这个类是我自己写的,如下:

package reflection;

public class Reflection {

	private int num = 0;
	private String s = "abc";

	public Reflection(){

	}

	public Reflection(int num, String s) {
		this.num = num;
		this.s = s;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getS() {
		return s;
	}

	public void setS(String s) {
		this.s = s;
	}
}

  这个类有四个方法,两个构造函数,两个私有变量。运行代码后,打印出的结果为:

public void setS(String)
public int getNum()
public String getS()
public void setNum(int)
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public Reflection()
public Reflection(int,String)

  可以看到,打印出了我所定义的全部方法和构造函数,而且打印出了基类的所以方法和构造函数。这个简单的例子,可以初步体会到反射的作用。下面我们来看个实际的例子。

二、Java代理和动态代理

  代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象带通信,因此代理通常充当着中间人的角色。下面的例子来自《java编程思想》,用来展示代理结构。
  这个是声明的接口

public interface Interface {
	void doSomething();
	void somethingElse(String arg);
}

  这个是一个实现了接口的实际类

public class RealObject implements Interface {

	@Override
	public void doSomething() {
		System.out.println("doSomething");

	}

	@Override
	public void somethingElse(String arg) {
		System.out.println("somethingElse " + arg);

	}

}

  这个是一个实现了接口的代理类

public class SimplyProxy implements Interface {
	private Interface proxied;

	public SimplyProxy(Interface proxy) {
		this.proxied = proxy;
	}

	@Override
	public void doSomething() {
		System.out.println("SimplyProxy doSomething");
		proxied.doSomething();
	}

	@Override
	public void somethingElse(String arg) {
		System.out.println("SimplyProxy somethingElse " + arg);
	}

}

  下面是测试文件代码

public class SimplyProxyDemo {
	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("bonobo");
	}
	public static void main(String[] args) {
		consumer(new RealObject());
		consumer(new SimplyProxy(new RealObject()));
	}
}

  下面是输出结果,比较简单,就不分析了。

doSomething
somethingElse bonobo
SimplyProxy doSomething
doSomething
SimplyProxy somethingElse bonobo

  Java动态代理比代理的思想更进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。下面是用动态代理重写的SimplyProxyDemo.java:

public class DynamicProxyHandler implements InvocationHandler{
	private Object proxied;
	public DynamicProxyHandler(Object proxied) {
		this.proxied = proxied;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
		System.out.println("**** proxy: " + proxy.getClass() + ", methods: " + method + ", args:" + args);
		if(args != null) {
			for(Object arg: args) {
				System.out.println(" " + arg);
			}
		}
		return method.invoke(proxied, args);
	}
}

class SimplyDynamicProxy {
	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("bonobo");
	}

	public static void main(String[] args) {
		RealObject real = new RealObject();
		consumer(real);
		Interface proxy = (Interface)Proxy.newProxyInstance(
				Interface.class.getClassLoader(),
			      new Class[]{ Interface.class },
			      new DynamicProxyHandler(real)
				);
		consumer(proxy);
	}
}

  输出结果如下:

doSomething
somethingElse bonobo
**** proxy: class com.sun.proxy.$Proxy0, methods: public abstract void proxy.Interface.doSomething(), args:null
doSomething
**** proxy: class com.sun.proxy.$Proxy0, methods: public abstract void proxy.Interface.somethingElse(java.lang.String), args:[Ljava.lang.Object;@6bc7c054
 bonobo
somethingElse bonobo

  通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(通常可以从已加载对象获得),一个你希望该代理实现的接口列表,以及InvocationHandler接口的一个实现。动态代理可以将所有的调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个实际的对象的引用,从而使得调用处理器在执行其中介任务时,可以实现转发。
  invode()方法中传递进来的代理对象,以防止你需要区分请求的来源,但是在许多情况下,你并不关心这一点。然而,在invode()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用。