概述
代理模式是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式有两种,静态代理和动态代理。
涉及角色
-
抽象角色:声明真实对象和代理对象的共同接口。
-
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
-
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
静态代理
首先有一个目标接口,有一个真实角色,然后手动构造代理类。案例如下
public interface Subject {
void request();
}
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("request...");
}
}
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("before request...");
realSubject.request();
}
}
静态代理的缺点:必须存在真实角色,必须手动书写代理类。不够灵活。
动态代理
动态代理机制的出现,使得开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,我门还可以按需调整委托类对象及其功能。
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。
沿用上面静态代理的案例,我们试着实现一个动态代理。
public class InvocationHandlerImpl implements InvocationHandler {
private RealSubject realSubject;
public InvocationHandlerImpl(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before request...");
method.invoke(realSubject, args);
return null;
}
}
public class Client {
public static void main(String[] args) {
//方式一:保存生成的代理类,运行后会自动在项目根目录生成com文件夹,其中com\sun\proxy下会生成$Proxy0.class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//真实角色
RealSubject realSubject = new RealSubject();
//委托处理器
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
//动态生成的代理对象
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Subject.class}, handler);
subject.request();
//方式二:保存JDK动态代理生成的代理类,类名保存为 ProxySubject
ProxyUtils.generateClassFile(realSubject.getClass(), "ProxySubject");
}
}
分析动态代理机制生成的代理类源代码
这里以上面的案例为例,生成的代理类源代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void request() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("Subject").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
分析:
- 必须存在业务接口
- 生成的代理类继承了Proxy类,这点可以得知,此代理类不可以再继承其它类
- 代理类实现了Subjuct接口,这是必须的。
- 代理类的构造函数中传入了InvocationHandler这个委托处理器,可以得知真正干活的是InvocationHandler。
- 代理类重写了目标接口所拥有的所有方法,然后内部全部委托给InvocationHandler对象的invoke方法去处理。
分析InvocationHandler委托处理器
这里主要看下它的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before request...");
method.invoke(realSubject, args);
return null;
}
可以看到这里直接调用了目标类(真实角色)的方法。然后这里可以实现一些hook技术,在真实角色方法调用前,做一些额外的操作。注意这里的invoke方法返回的object是对应代理类中方法的返回值。所以注意返回类型一致。
动态代理机制的优点:比较灵活,不需要我们手动书写代理类。
如何保存生成的代理类字节码
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyUtils {
/**
* 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
* params: clazz 需要生成动态代理类的类
* proxyName: 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz, String proxyName) {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}