把《Java核心技术》与《Java编程思想》翻过去。反射一部分讲的大同小异。总体来说《Java核心技术》讲得比较拖沓,但是比较全面,《Java编程思想》讲得比较简略,第一遍看可能看不懂。
因为之前有仔细研究过几个框架中XML配置文件的语法,发现反射中代理类的应用在框架中虽然是透明的,但是我依然能够感受它的存在。我甚至怀疑AOP的对象不是由编译器生成合成对象实现的,而是由代理对象实现的,因为面向接口编程的思想到处都是。
一、 什么是代理类。
现实中不可能为每个接口都准备好一个适配器类,毕竟Java并不支持多基类继承。有时候我们可能需要动态地生成一个对象,它的内部实现只是对某些方法的一个封装。通过这些封装我们可以实现AOP,比如在某个类的方法执行前、执行后写日志等等。它包含一个类加载器、一些接口列表和一个调用处理器类。
一句话来讲,代理类就是在接口和接口实现类之间加上一个中间层,实现一些中间层特有的操作。
二、如何构造一个调用处理器类?
1 代理类必须实现InvocationHandler接口,幸运的是,我们只需要重写它的一个抽象方法--Invoke()。
2 这个代理类中必须有一个局部动态代理对象--注意区分,代理处理类中还自带一个局部动态代理对象。这一般可以通过构造器获得。
3 Invoke方法的写法:
参数1 proxy对象。
参数2 一个method对象--这是一个高阶函数式的写法。
参数3 一个arg数组。
必须抛出Throwable类型的异常。
函数体内:
(1) 加入自己想要的内容。
(2) return method.invoke(proxy,args)。即使用反射的方法确实地执行这个方法method。
一句话来讲,调用处理类就是必须以接口实现类为构造参数,对接口的每一个方法都要invoke一下的类。
三、如何创建动态代理对象?
使用静态工厂的方法,Proxy.newProxyInstance()。
它的参数:
1 一个类加载器,一般使用一个已经被加载的对象a上使用 a.class.getClassLoader()获取。
2 一个希望实现的接口列表。注意,是一个Class类型的数组。
3 以及一个调用处理器的对象,必须用一个实际上实现了接口的引用对象对它进行初始化。
创建完毕后,记得将它转型为想要实现的某个接口类型,才能给引用赋值。
四、动态代理对象如何工作?
当这个代理对象x被赋予某种接口a的引用,然后调用a.b()方法时,虚拟机会执行以下操作:
1 将请求转发到这个动态代理对象的调用处理器类。这个调用处理器类的构造器获得x的引用,将它赋予自己的局部动态代理对象X。b
2 执行invoke方法,第一个参数传入局部动态代理对象X,第二个参数传入b(),第三个参数传入接口调用时赋予的参数(如果有的话)。
3 执行invoke方法体,退出。
每次调用x的任何一个方法,invoke方法都会被调用,每次调用第一个参数都是一样的。
刚写的一段示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
* 这个类很像AOP的一种用法,在方法执行以前加入某些操作,只有中间层才能做到。*/
interface A
{
void b();
void c(String d);
}
class E implements A
{
public void b()
{
System.out.println("This is b.");
}
public void c(String d)
{
System.out.println("This is c's d" + d);
}
}
class myInvocationHandler implements InvocationHandler
{
private Object obj;
public myInvocationHandler(Object obj)
{
this.obj = obj;
}
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable
{
System.out.println("This is the object of " + proxy.getClass().getName());
System.out.println("The method name is " + method.getName());
if(args != null)//如果没有这个句子,会出现一个空指针异常。
for(Object arg : args)
System.out.println("The argument is " + arg);
return method.invoke(obj, args);//极度要注意,这里的obj与proxy不同,proxy的传值我们没有办法控制,我们只能控制object的。
}
}
public class MyProxy {
/**
* @param args
*/
public static void main(String[] args) {
E e = new E();
A a = (A) Proxy.newProxyInstance(e.getClass().getClassLoader(),//这个classloader的获取做法很奇怪。但是却不能用null。 如果不是强制转换为A类型的话,就可以了。
new Class[] {A.class},
new myInvocationHandler(e));//这就把e传进调用处理器里面去了。
Object b = Proxy.newProxyInstance(null,new Class[] {Comparable.class }, new myInvocationHandler(e));//如果要实现的接口不是自定义的,就可以使用null作为类加载器了。如果使用a.class,则必须使用a.getClass.getClassLoader()方法。
a.b();
a.c("liang");
a.c("123");
}
}