动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。
怎么理解呢?
就是直接传入需要代理的目标对象A,生成一个新的目标对象B,这个新的目标对象B就是代理对象,具备A所以信息,并且通过相关API可以对A对象都相关方法做一个功能的增强:比如你想在执行A方法前加一段代码逻辑,或者执行A方法后,加一段代码逻辑。
JDK动态代理呢,主要涉及的一些类和方法
一个时JDK自带的java.lang.reflect Proxy,主要涉及的方法呢是
static Object newProxyInstance(
ClassLoader loader, //指定当前目标对象使用类加载器
//这里可以回顾下 JVM类加载过程,这个类加载的功能是什么?
Class<?>[] interfaces, //目标对象实现的接口的类型
//这个有是用来干嘛的呢?
InvocationHandler h //事件处理器
)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
还有java.lang.reflect InvocationHandler,主要用到的方法是
Object invoke(Object proxy, Method method, Object[] args)
// 在代理实例上处理方法调用并返回结果。
那么怎么用呢?这先举个简单的例子,看看简单的使用流程
举例:保存用户功能的静态代理实现
1.创建一个代理工厂类
/**
* 代理工厂-动态生成代理对象
* @author spikeCong
* @date 2022/9/22
**/
public class ProxyFactory {
private Object target; //维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance(){
//使用Proxy获取代理对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标类使用的类加载器
target.getClass().getInterfaces(), //目标对象实现的接口类型
new InvocationHandler(){ //事件处理器
/**
* invoke方法参数说明
* @param proxy 代理对象
* @param method 对应于在代理对象上调用的接口方法Method实例
* @param args 代理对象调用接口方法时传递的实际参数
* @return: java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
//执行目标对象方法
method.invoke(target, args);
System.out.println("提交事务");
return null;
}
}
);
}
}
//测试
public static void main(String[] args) {
IUserDao target = new UserDaoImpl();
System.out.println(target.getClass());//目标对象信息
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass()); //输出代理对象信息
proxy.save(); //执行代理方法
}
然后来分析下这个代理类是如何动态生成的呢?
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
-
通过一个类的全限定名来获取定义此类的二进制字节流
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口
由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:从本地获取、从网络中获取、运行时计算生成
运行时计算生成:
这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy
的代理类的二进制字节流
所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用
然后
我们通过借用阿里巴巴的一款线上监控诊断产品 Arthas(阿尔萨斯) ,对动态生成的代理类代码进行查看
将这个代理对象的字节码文件,反编译后可以得到代理类的代码,如下:
package com.sun.proxy;
import com.mashibing.proxy.example01.IUserDao;
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 IUserDao {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", 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());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void save() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
简化后的代码:
package com.sun.proxy;
import com.mashibing.proxy.example01.IUserDao;
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 IUserDao {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);
return;
}
}
public final void save() {
try {
this.h.invoke(this, m3, null);
return;
}
}
}
首先分析下这个类名$Proxy0;可以看到呢,它继承了Proxy,实现了我们传入的接口IuserDao,是不是和我们要代理的目标对象很像很像,只不过多继承了一个类Proxy,然后看构造器方法:传入
的是InvocationHandler,这个也是我们之前传入的一个方法参数。
-
动态代理类对象 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
-
代理类的构造函数,参数是
InvocationHandler
实例,Proxy.newInstance
方法就是通过这个构造函数来创建代理实例的 -
类和所有方法都被
public final
修饰,所以代理类只可被使用,不可以再被继承 -
每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以
m + 数字
的格式命名 -
调用方法的时候通过
this.h.invoke(this, m3, null));
再来看下生成代理对象的Proxy类,最表层的生成实例源代码
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) {
try {
Class clazz = getProxyClass(loader, interfaces);//获取对应的class对象
//通过class对象的构造器、newInstance生成我们的代理对象实例
return clazz.getConstructor(InvocationHandler.class).newInstance(h);
} catch (RuntimeException var4) {
throw var4;
} catch (Exception var5) {
throw new CodeGenerationException(var5);
}
}
-
实际上 h.invoke就是在调用ProxyFactory中我们重写的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
//执行目标对象方法
method.invoke(target, args);
System.out.println("提交事务");
return null;
}