代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
- 一个接口的实现在编译时无法知道,需要在运行时才能实现。
- 实现某些设计模式:适配器(Adapter)或修饰器(Decorator)。
- 面向切面编程:如AOP in Spring。
让我们先看看一个简单的例子。
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(instance2Proxy);
// 方法一,通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
// 通过构造函数对象创建动态代理类实例
Foo f = (Foo)constructor.newInstance(new Object[] { handler });
// 方法二,实际上是对方法一的封装,调用Proxy的newProxyInstance方法,并传入相关的几个参数。
Foo f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] {Foo.class}, handler);
简而言之,Java 实现动态代理需要四个步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器,注意处理器对象构造时需要传入委托类对象;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:
- java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
- java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
- java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
Proxy 的静态方法:
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
InvocationHandler h)
InvocationHandler 的核心方法:
// 该方法负责集中处理动态代理类上的所有方法调用,共有三个参数:
// 1. 动态代理类的引用,通常情况下不需要它。但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,如方法列表,annotation等。
// 2. 方法对象的引用,代表被动态代理类调用的方法。从中可得到方法名,参数类型,返回类型等等
// 3. args对象数组,代表被调用方法的参数。注意基本类型(int,long)会被装箱成对象类型(Interger, Long)
Object invoke(Object proxy, Method method, Object[] args)
- 代理类的名字一般形如“$Proxy0”...“$ProxyN”,也就是前缀“$Proxy”加一个数字。
- 代理类继承了java.lang.reflect.Proxy类,并实现了传入的interfaces对象的接口。
- 该代理类实际上是通过sun.misc.ProxyGenerator类的generateProxyClass方法产生的。
- 代理类调用相关的代理方法时,通过反射机制调用处理器对象的invoke方法,实现增加代理逻辑的目的。
Proxy类的关键代码
// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap();
// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object();
// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 关联的调用处理器引用
protected InvocationHandler h;
// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {}
// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;}
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 检查 h 不为空,否则抛异常
if (h == null) {
throw new NullPointerException();
}
// 关键之一
// 获得与制定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces);
// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException {
// 1,通过 Class.forName 方法判接口的可见性
try {
// 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
// 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
// 2,缓存表的使用
do {
// 以接口名字列表作为关键字获得对应 cache 值
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果已经创建,直接返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
// 代理类正在被创建,保持等待
try {
cache.wait();
} catch (InterruptedException e) {
}
// 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
continue;
} else {
// 标记代理类正在被创建
cache.put(key, pendingGenerationMarker);
// break 跳出循环已进入创建过程
break;
} while (true);
// 关键之二
// 3,动态生成代理类
// 动态地生成代理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
try {
// 动态地定义新生成的代理类
proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0,
proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
// 把生成的代理类的类对象记录进 proxyClasses 表
proxyClasses.put(proxyClass, null);
// 4,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,
// 否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。
// ...
}
通过阅读源码能够找到两个关键的地方,其一是获得与制定类装载器和一组接口相关的代理类类型对象,即 getProxyClass 方法;其二是在getProxyClass方法里调用 ProxyGenerator 类的 generateProxyClass 方法动态地生成代理类的字节码数组。最后 getProxyClass 返回了一个继承 Proxy 类和实现了委托类相关接口的子类对象。
下面来看看这个动态生成的Proxy子类:
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode",
new Class[0]);
m3 = Class.forName("***.RealSubject").getMethod("request",
new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString",
new Class[0]);
} catch (NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
} catch (ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
} //static
public $Proxy0(InvocationHandler invocationhandler) {
super(invocationhandler);
}
@Override
public final boolean equals(Object obj) {
try {
return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final int hashCode() {
try {
return ((Integer) super.h.invoke(this, m0, null)).intValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void request() {
try {
super.h.invoke(this, m3, null);
return;
} catch (Error e) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final String toString() {
try {
return (String) super.h.invoke(this, m2, null);
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
补充 Subject 接口:
//抽象角色(动态代理只能代理接口)
public interface Subject {
public void request();
}
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事.
- 根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0.$Proxy0类 实现了interfaces的接口,并继承了Proxy类.
- 实例化$Proxy0并在构造方法中把DynamicSubject传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值。
接着把得到的$Proxy0实例强制转换成Subject,并将引用赋给subject。当执行subject.request()方法时,就调用了$Proxy0类中的request()方法,进而调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()。
其实,Java动态代理的关键是“动态”,也就是动态的生成 Proxy 类的子类对象,而这个子类对象实际上就是代理类对象,同时实现了委托类相关的接口。遗憾的是,由于继承了 Proxy 类和Java单继承的特点,在 Java 动态代理机制中无法实现对类的代理。不过,通过扩展相关接口或者重新设计,我们也很容易在 Java 中实现类的动态代理。
