代理简介?
我们知道nginx可以实现正向,反向代理。比如我们想请求服务中一个tomcat,一般就是直接访问机器的ip,如果是代理的话,就是先访问中间代理层(nginx),然后nignx跳转到我们的tomcat机器。代理模式也是如此,也有一个Proxy层,通过Proxy层来真正访问我们的类接口。
为什么要有代理?
我们先看nginx实现的代理,他可以事先为我们做很多ip黑名单过滤,负载均衡,权限,甚至我们还可以到代理层改变我们http接口信息。java的Proxy也是如此,可以在访问真正类的时候做一些前置和后置的统一工作。
JDK的动态Proxy实现
比如有个相亲代理机构,java程序员们通过这个代理机构来找老婆,java程序员有个需求,对象必须是A照杯 的才行,这些就可以统一交给相亲代理机构来做,自己等结果就行了。
代码实现
public class Main {
// 相亲代理机构
static class FindWomanProxy implements InvocationHandler{
// 被代理的对象
private FindWoman woman;
public FindWomanProxy(FindWoman woman) {
this.woman = woman ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在真实的对象执行之前我们可以添加自己的操作
System.out.println("相亲代理正在筛选A照杯的女士");
// 筛选 完成 交给 程序员去消费相亲
return method.invoke(woman, args);
}
}
// 相亲 接口
static interface FindWoman {
public void find();
}
// java 程序员相亲
static class JavaFindWoman implements FindWoman{
@Override
public void find() {
System.err.println("java程序员相亲 ");
}
}
public static void main(String[] args) {
// 构造相亲代理 , 把java程序员传进去
FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman());
// 机构产出的相亲代理对象,并非传入的 JavaFindWoman 。
FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy);
findWoman.find();
}
}
代码解释: java程序员 通过相亲代理机构(FindWomanProxy) 去完成相亲这件事(FindWoman)。代理机构 产出代理对象,然后调用代理对象的相亲方法,会执行筛选A照杯 ,然后java程序员真正进行相亲。
代理的好处
以后PHP程序员相亲,只要在相亲机构中传入PHP程序员就行了。
FindWomanProxy proxy = new FindWomanProxy(new PhpFindWoman());
而且我们要更改 相亲机构 的 筛选 “照杯” 算法也很简单,统一就改了。还有一个很重要的好处:
发现没有,我们JavaFindWoman类是不是 很干净, 没有丝毫的 筛选A照杯 算法 代码。也就是说 可以 实现无侵入式的代码扩展 。
我很好奇,那个 Proxy.newProxyInstance 方法 是是怎么动态生成代理对象的?生成的代理对象字节码又是什么样子? 于是我继续进行研究。
Proxy.newProxyInstance 实现原理
通过调式jdk源码,发现了内部用了缓存来缓存生成的class,不是每一次都生成,最终生成class的代码在apply里面(缓存部分的我就不讲了)
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成的class 前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 名字的自增 标识
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
// 加载传入的接口 也就是我们上面的相亲接口 FindWoman.class
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
// 不是接口抛出异常。 所以JDK只能代理接口
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
///这段代码 就是为了产生 proxyPkg 包名
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// proxyPkg 为 传入接口的 所在包名称
//proxyClassNamePrefix : 固定值 $Proxy
// num : 自增
// 生成的代理类名称 包名.$Proxy0
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* sun.misc.ProxyGenerator 工具 生成类 的字节流
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 将class字节流 加载到jvm , 从而生成class
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
}
从上面看出 其实也是借助了 sun.misc.ProxyGenerator 工具 生成class字节流,然后通过native方法 defineClass0 加载到jvm生成class的。而且上面判断了只能代理接口。
上面拿到class 之后 ,然后通过反射,就构造出了代理对象了
// cl 为上面产生的 class
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 反射 构造实例
return cons.newInstance(new Object[]{h});
现在我觉得我最关注的就是这个class 里面内容到底是什么? jdk提供一个参数让我们把class的文件download出来
public static void main(String[] args) throws IOException {
//生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 构造相亲代理 , 把java程序员传进去
FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman());
FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy);
findWoman.find();
}
sun.misc.ProxyGenerator.saveGeneratedFiles 设置为true后,会生成class文件
我们通过反编译工具 jd-gui 就可以看到 class内容
import debug_jdk8.;
import debug_jdk8.Main;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class Proxy0 extends Proxy implements Main.FindWoman {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void find() {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
内部确实很用心, 把toString,hashCode,equals方法都代理了。也继承了 Proxy类,实现了我们的接口。Proxy类:
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
...
}
我们就关注下我们代理 的 find 方法吧
public final void find() {
try {
// h : InvocationHandler 也就是我们的相亲代理机构 FindWomanProxy
// m3
this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
// 代理的接口的 find 方法 Method对象
m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
也就是回调了我们的代理机构的 InvocationHandler 方法了
这样当调用代理对象的find 方法时 , 也就回调到上面这个红色的方法了。到此,面纱已经解开。我们在来自己实现一个简易的JDK动态Proxy。
自己实现JDK 动态Proxy
我们不用 sun.misc.ProxyGenerator 来生class了,我们用 Javassist 技术动态生成class。
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。它可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。
算了,下面还有很多内容要讲,我另起炉灶吧,要想自己实现一个JDK的动态代理,请您移步到这。