动态代理、动态代理、动态代理,首先它是动态的,然后他能实现代理模式。
“动态”、“代理”,这两个词将贯穿全文。
java的动态代理,首先它是用来实现**“代理模式**”的。
然后他是动态的可以灵活的代理**“任何类”**【当然这个类需要有接口】。
我们先来看看动态代理能实现的效果。
如上示意图,类TestA、TestB想调用TargetObject0、TargetObject1……等目标对象的任何方法,都需要经过InvocationHandler的invoke方法。
注意这是说的是,TargetObject对象的任何方法
注意这是说的是,TargetObject对象的任何方法
注意这是说的是,TargetObject对象的任何方法
为什么说他是动态的呢?
首先,InvocationHandler类可以代理几乎任一类的的几乎任一方法【构造方法不行】
然后,java是通过动态创建代理类,来实现上述代理功能。
没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。
没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。
没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。
文章后面,我们再细聊,java如何通过,动态创建代理类,来实现任一类代理功能。
直接上代码,代码不难。我们自顶向下,先看main方法。
public class DynamicProxyMain {
public static void main(String[] args) {
//1)创建目标对象
TargetObject targetObject = new TargetObject();
//2)创建代理对象
ITarget proxyObject = (ITarget) Proxy.newProxyInstance(
TargetObject.class.getClassLoader(),
TargetObject.class.getInterfaces(),
new DynamicProxyHandler(targetObject)//注意DynamicProxyHandler在这
);
//3)通过代理对象proxyObject,调用目标对象targetObject的方法getVoid()
proxyObject.getVoid();// ITarget接口方法的调用,直接代理到DynamicProxyHandler.invoke()方法
}
}
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----调用开始,对象:" + target + "; 方法:" + method.getName());
//通过反射调用目标对象的方法,当然也可以不调用目标对象的方法,完全由你决定
Object ret = method.invoke(target, args);
System.out.println("----调用结束,对象:" + target + "; 方法:" + method.getName() + ",返回值" + ret+ "\n");
return ret;
}
}
目标类的定义
interface ITarget {
void getVoid();
}
class TargetObject implements ITarget {
//注意一定要实现接口ITarget
public void getVoid() {
System.out.print("TargetObject.getVoid();\n");
}
}
Demo日志输出:
----调用开始,对象:com.java.study.base.proxy.TargetObject@6f94fa3e; 方法:getVoid
TargetObject.getVoid();
----调用结束,对象:com.java.study.base.proxy.TargetObject@6f94fa3e; 方法:getVoid,返回值null
注意,这里需要强调下:
被代理的目标类TargetObject必须有接口:也就是ITarget接口。
那么,Proxy.newProxyInstance()方法创建了一个什么对象?
接着我们稍微该下main方法:
public class DynamicProxyMain {
public static void main(String[] args) {
Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));
//我们一起来看看,动态代理proxyObject的类是什么?
//下面的日志将会输出什么?
System.out.println("\n\nproxyObject的类=" + proxyObject.getClass().getSimpleName());
System.out.println("proxyObject实现的接口=" + Arrays.toString(proxyObject.getClass().getInterfaces()));
}
}
上面main函数中的日志将会输出什么?
上面main函数中的日志将会输出什么?
上面main函数中的日志将会输出什么?
public class DynamicProxyMain {
public static void main(String[] args) {
Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));
//我们一起来看看,动态代理proxyObject的类是什么?
System.out.println("\n\nproxyObject的类=" + proxyObject.getClass().getSimpleName());
//日志输出:proxyObject的类=$Proxy0
System.out.println("proxyObject实现的接口=" + Arrays.toString(proxyObject.getClass().getInterfaces()));
//日志输出:proxyObject实现的接口=[interface com.java.study.base.proxy.ITarget]
}
}
从日志我们可看到,Proxy.newProxyInstance()返回的对象的类是 $Proxy0,并且这个对象实现了接口ITarget。
P r o x y 0 ∗ ∗ 类 就 是 我 们 前 面 提 到 的 , j a v a 动 态 创 建 的 类 。 ∗ ∗ Proxy0**类就是我们前面提到的,java动态创建的类。** Proxy0∗∗类就是我们前面提到的,java动态创建的类。∗∗Proxy0类就是真正的代理类,通过这个对象调用 的方法都会代理到 DynamicProxyHandler的invoke方法。
DynamicProxyHandler的invoke方法,可以决定是否调用TargetObject的对应方法,也可以在调用之前和调用之后做些特殊处理。
我们再来梳理下Proxy.newProxyInstance()方法:
public static Object newProxyInstance(
ClassLoader loader, //动态创建的代理类$Proxy0,会加载到loader中
Class<?>[] interfaces,//动态创建的代理类$Proxy0,将implement所有interfaces中的接口
InvocationHandler h)//创建的代理对象的方法调用都将代理到InvocationHandler.invoke()方法
我们继续梳理一下上面demo的类图
那么大叔要问一个问题了:
上面的demo,代理类,$Proxy0,只能代理ITarget接口定义的方法吗?
代理类,$Proxy0,只能代理ITarget接口定义的方法吗?
代理类,$Proxy0,只能代理ITarget接口定义的方法吗?
代理类,$Proxy0,只能代理ITarget接口定义的方法吗?
如果调用Object.equals()是否能代理到DynamicProxyHandler.invoke()方法呢?
如下demo:
public static void main(String[] args) {
Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));
);
proxyObject.equals(proxyObject);// 这个equals方法是否会代理到DynamicProxyHandler.invoke()方法?
}
答案是肯定的。proxyObject.equals()、proxyObject.hashCode()、proxyObject.toString()等都会代理到DynamicProxyHandler.invoke()方法。
我们来跟下java8的源码。
注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk
注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk
注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk
public class Proxy implements java.io.Serializable {
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//...
//1)生成$Proxy0类的字节码,存储在byte数组中[proxyClassFile]
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//2)将$Proxy0类的字节码解析成Class对象返回,并将$Proxy0类加载到ClassLoader中。
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
}
于是我们找到ProxyGenerator类的源码。https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/sun/misc/ProxyGenerator.java
public class ProxyGenerator {
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
//...
return var4;
}
/**
* 重点在generateClassFile()函数;
* 重点在generateClassFile()函数;
* 重点在generateClassFile()函数;
* 我们可以看到:
* 以流的方式[DataOutputStream]一个个字节写入,字节码。
* 然后导出,DataOutputStream.toByteArray()导出byte[]。
**/
private byte[] generateClassFile() {
//...
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
//开始将字节码写入dout流 ------ 如果想细究java的字节码文件格式的同学可以谷歌下:java字节码文件
dout.writeInt(0xCAFEBABE);//0xCAFEBABE,看这里看这里,先写了4个字节:字节码的魔术
dout.writeShort(CLASSFILE_MINOR_VERSION);//继续又写2个字节:minor version次版本
dout.writeShort(CLASSFILE_MAJOR_VERSION);//继续又写2个字节:major version 主版本
cp.write(dout);
dout.writeShort(accessFlags);
dout.writeShort(cp.getClass(dotToSlash(className)));
dout.writeShort(cp.getClass(superclassName));
dout.writeShort(interfaces.length);
//...
//
return bout.toByteArray();
}
}
我们继续找到Proxy的defineClass0()函数的源码
public class Proxy implements java.io.Serializable {
//返现defineClass0()函数是native的
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
}
靠,Proxy.defineClass0()方法是c写的。
靠,Proxy.defineClass0()方法是c写的。
靠,Proxy.defineClass0()方法是c写的。
不慌不忙,继续找到他的c源码。
我们拿出大学时代学习的c语言知识。
接着看:Proxy.c
#include
#include
#include "jvm.h"
#include "io_util.h"
JNIEXPORT jclass JNICALL
Java_java_lang_reflect_Proxy_defineClass0(JNIEnv *env,
jclass ignore,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length)
{
//...
jbyte *body;
char *utfName;
body = (jbyte *)malloc(length);
//...
//1)将字节码data数组,拷贝到body数组;
//原则上java虚拟机,数组的内存分配在堆上,由java的GC统一释放,所以c要访问java的数组一般都需要做下处理下。
(*env)->GetByteArrayRegion(env, data, offset, length, body);
//...
//2)下面可以简单理解为:将java的字符串name,拷贝到c的字符串指针上utfName。
if (name != NULL) {
jsize len = (*env)->GetStringUTFLength(env, name);
jsize unicode_len = (*env)->GetStringLength(env, name);
if (len >= (jsize)sizeof(buf)) {
utfName = malloc(len + 1);
if (utfName == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
goto free_body;
}
} else {
utfName = buf;
}
(*env)->GetStringUTFRegion(env, name, 0, unicode_len, utfName);
VerifyFixClassname(utfName);
} else {
utfName = NULL;
}
//3)关键方法在这里
//关键方法在这里
//关键方法在这里
// DefineClass()函数,构建:代理类。并返回。
// 这个方法是jni的标准api,好了JNIEnv的源码我们就不深挖了。
result = (*env)->DefineClass(env, utfName, loader, body, length);
//...
return result;
}
最后我们用一句话,总结一下:
java的动态代理的核心是,通过Proxy.newProxyInstance(classLoader,interfaces,invocationHandler)动态创建代理类,将所有函数调用代理到invocationHandler里。
动态代理有一定的局限性,就是代理的对象必须有接口。
动态代理的使用场景非常少,正常开发基本很少用到动态代理,尤其android这种客户端开发就更少了。
大叔一直很迷惑,为什么有些面试官很喜欢问动态代理,理解java动态代理,能说明什么呢?
如果可以的话,大叔希望动态代理了,您的点赞功能。
可惜不行,那就主动点个赞 再走吧,老铁。
原文链接https://juejin.im/post/6874624518329991176