文章目录
- 基本概念
- 入门案例
- 实现JDK动态代理的步骤
- 入门实操
- 拓展--动态生成代理类的几种方法
- 方式一:通过getProxyClass方法获取代理实例
- 方式二:通过newProxyInstance方法获取代理实例(常用!)
- 方式三:通过Lambda表达式简化实现
- 生成并查看JDK动态代理类的的字节码
- JDK8及之前版本
- Java 9及之后
- 如何查看?
- 如何确定版本?
- 为什么这样设置就可以生成代理类字节码到本地?
- 底层原理解析
- 代理类的创建
- `Proxy.newProxyInstance`方法分析
- 代理类$Proxy是什么样子
- 注意的地方
在我们日常开发中,代理模式是一种非常常见也非常有用的设计模式。它能够为其他对象提供一种代理以控制对这个对象的访问( 说人话就是,增强我们原有对象的功能,在原有对象基础上增加一些我们自己要的操作,比如:事务处理、记录日志等)。在 Java 中,代理可以是 静态的也可以是 动态的。接下来我将深入探索 JDK 中的动态代理——会从基础概念讲起,通过入门示例到探究其实现原理,助你彻底理解动态代理。
基本概念
JDK动态代理是一种在运行时创建代理对象的技术,它允许我们在不修改目标类代码的情况下,增强目标对象的功能。这种代理模式主要用于实现AOP(面向切面编程)
入门案例
实现JDK动态代理的步骤
- 接口的定义:首先,需要有一个或多个接口来定义代理类的行为。这些接口将被代理类实现,从而确保代理类能够执行目标对象的方法
- 创建代理类:使用
Proxy
类和InvocationHandler
接口来创建代理类。Proxy
类提供了静态方法newProxyInstance()
,它接受三个参数:类加载器、接口数组和一个InvocationHandler
实例。通过这个方法,可以在运行时动态地创建代理类的实例 - 编写InvocationHandler:
InvocationHandler
是一个接口,用于处理代理对象的调用。开发者需要实现这个接口,并在其中定义如何处理目标对象的方法调用。这包括了方法的增强处理,如日志记录、事务管理等 - 注册InvocationHandler:通过
Proxy.newProxyInstance ()
方法创建代理对象时,传入的InvocationHandler
实例就是用来注册这个处理程序的。这样,当代理对象的方法被调用时,就会触发InvocationHandler
中的逻辑 - 使用代理对象:最后,可以像使用普通对象一样使用代理对象。由于代理对象实现了目标接口,因此可以调用其方法。当这些方法被调用时,实际上是调用了代理对象内部的增强逻辑
入门实操
首先,我们需要定义一个接口,例如 ServiceInterface
,其中包含一些方法,比如 execute
。同时我们创建一个实现了 ServiceInterface
接口的类,例如 ServiceImpl
,并实现这些方法。
// 定义服务接口
public interface ServiceInterface {
void execute();
}
// 实现服务接口
public class ServiceImpl implements ServiceInterface {
public void execute() {
System.out.println("执行服务方法");
}
}
现在,我们将使用动态代理来创建代理对象。我们需要实现一个 InvocationHandler
接口,并重写它的 invoke
方法。
// 实现 InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前的代理操作...");
// 通过反射调用目标方法
Object result = method.invoke(target, args);
System.out.println("方法执行后的代理操作...");
return result;
}
}
现在,我们可以编写一个测试类来验证动态代理的使用,这个例子中,我们通过动态代理实现了一个代理对象,当调用代理对象的方法时,会先执行 invoke
方法中的前置处理逻辑,然后再调用目标对象的方法,最后执行后置处理逻辑。
-
其中
Proxy.newProxyInstance
动态生成代理并使用 -
注意这行代码
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
是生成动态代理类的字节码的,接下来我们讲讲有几种方式生成!
// 动态生成代理并使用
public class ProxyTest {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
ServiceInterface service = new ServiceImpl();
InvocationHandler handler = new MyInvocationHandler(service);
ServiceInterface proxyInstance = (ServiceInterface) Proxy.newProxyInstance(
ServiceInterface.class.getClassLoader(),// 指定类加载器
new Class<?>[] {ServiceInterface.class},// 指定要代理的接口
handler);// 指定InvocationHandler
// 执行的是代理的方法
proxyInstance.execute();
}
}
拓展–动态生成代理类的几种方法
方式一:通过getProxyClass方法获取代理实例
- 根据类加载器和接口数组获取代理类的Class对象
- 过Class对象的构造器创建一个实例(代理类的实例)
- 将代理实例强转成目标接口ServiceInterface(因为代理类实现了目标接口,所以可以强转)。
- 最后使用代理进行方法调用。
@Test
public void test1() throws Exception {
ServiceInterface service = new ServiceImpl();
// 根据类加载器和接口数组获取代理类的Class对象
Class<?> proxyClass = Proxy.getProxyClass(ServiceInterface.class.getClassLoader(), ServiceInterface.class);
// 通过Class对象的构造器创建一个实例(代理类的实例)
ServiceInterface serviceInterfaceProxy = (ServiceInterface) proxyClass.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler(ServiceInterface));
// 调用 execute 方法
String value = serviceInterfaceProxy.execute();
}
方式二:通过newProxyInstance方法获取代理实例(常用!)
也就是我们上面的例子,通过这种方法是最简单的,也是推荐使用的,通过该方法可以直接获取代理对象。
注:其实该方法后台实现实际与上面使用getProxyClass方法的过程一样。
ServiceInterface service = new ServiceImpl();
InvocationHandler handler = new MyInvocationHandler(service);
ServiceInterface proxyInstance = (ServiceInterface) Proxy.newProxyInstance(
ServiceInterface.class.getClassLoader(),
new Class<?>[] {ServiceInterface.class},
handler);
// 执行的是代理的方法
proxyInstance.execute();
方式三:通过Lambda表达式简化实现
其实InvocationHander
接口也不用创建一个实现类,可以使用Lambad表达式进行简化的实现,如下代码:
@Test
public void test3() {
ServiceInterface service = new ServiceImpl();
ServiceInterface proxyInstance = (ServiceInterface) Proxy.newProxyInstance(
ServiceInterface.class.getClassLoader(),
new Class<?>[] {ServiceInterface.class},(proxy, method, args1) ->{
System.out.println("方法执行前的代理操作...");
// 通过反射调用目标方法
Object result = method.invoke(service, args);
System.out.println("方法执行后的代理操作...");
return result;
}
);
// 执行的是代理的方法
proxyInstance.execute();
}
生成并查看JDK动态代理类的的字节码
在标准的Java JDK实现中,生成的动态代理类(字节码)默认是在内存中动态生成并直接加载的,不会写入磁盘成为文件。所以,通常我们无法直接获取到这些字节码文件。
不过,有一种办法可以让JVM生成的代理类字节码被保存到磁盘上,这是通过设置系统属性来实现的。可以在启动Java应用程序时通过命令行设置系统属性
JDK8及之前版本
在Java 8及以下版本,可以在idea启动时这样设置属性:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在程序中,可通过以下代码进行设置:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Java 9及之后
对于Java 9及之后的版本,由于模块化系统的引入,该系统属性可能不再起作用,相应的系统属性变更为:
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
或者在Java代码中进行设置:
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
添加了这个系统属性后,当创建动态代理实例的时候,JVM会将生成的代理类以 .class
文件形式保存在工程的根目录或者 com/sun/proxy
目录下。
如何查看?
为了调试目的,我们要查看或修改这些动态生成的代理类,正常我们还需要反编译工具(比如JD-GUI或JAD)来查看生成的类的源代码,但是我们不用那么麻烦,直接用IDEA打开即可
如何确定版本?
如果还有人不知道如何确定是sun.misc.ProxyGenerator.saveGeneratedFiles
还是jdk.proxy.ProxyGenerator.saveGeneratedFiles
,可以打开sun.misc.ProxyGenerator
类查看,注意这个变量saveGeneratedFiles
boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
为什么这样设置就可以生成代理类字节码到本地?
还是在sun.misc.ProxyGenerator
这里,搜索查看sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
这个方法,可以发现上面设置完了后,我们的saveGeneratedFiles是true,此时就会生成对应的字节码到本地图中那个路径com/sun/poxy
我的项目中生成如下
底层原理解析
JDK动态代理是基于Java的反射机制实现的。在创建代理对象时,JDK会动态生成一个新的类,实现了目标对象所实现的接口,并将代理逻辑写入 invoke
方法中。当调用代理对象的方法时,实际上是调用了 invoke
方法,然后由 invoke
方法根据方法名来调用目标对象的方法。
代理类的创建
Java动态代理的实现是通过 java.lang.reflect.Proxy
类来完成的。当我们调用 Proxy.newProxyInstance
方法时,它会做以下几件事情:
- 检查提供的接口是否全部为公共接口。
- 确定要使用的类加载器。
- 生成代理类的二进制字节码。
- 加载这些二进制字节码,定义代理类。
ServiceInterface proxyInstance = (ServiceInterface) Proxy.newProxyInstance(
ServiceInterface.class.getClassLoader(),// 指定类加载器
new Class<?>[] {ServiceInterface.class},// 指定要代理的接口
handler);// 指定InvocationHandler
// 执行的是代理的方法
proxyInstance.execute();
生成的代理类将会继承 Proxy
类,并实现了指定的所有接口方法。在这个过程中,JVM将使用一个叫做 $Proxy0
的类实现了 ServiceInterface
接口(序号可能会根据实际生成的代理数量不同而变化)。这个代理类从 Proxy
继承了很多处理逻辑,同时也将接口方法全部委托给 InvocationHandler
处理。
Proxy.newProxyInstance
方法分析
public class Proxy {
// 获取或创建一个动态代理类的实例的静态方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 确保提供的InvocationHandler不为null
Objects.requireNonNull(h);
// 克隆接口数组,以避免对原数组的潜在修改
final Class<?>[] intfs = interfaces.clone();
// 获取系统的安全管理器
final SecurityManager sm = System.getSecurityManager();
// 如果有安全管理器,检查创建代理实例的权限
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 查找或生成指定的代理类。
*/
// 获取代理类
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 调用其构造函数并用指定的调用处理器InvocationHandler作为参数。
*/
try {
// 如果安全管理器存在,进一步检查权限
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 查找代理类的构造函数,这个构造函数预期接收一个InvocationHandler作为参数
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 如果代理类不是public的,需要特权访问权限,以便设置构造函数为可访问状态
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 使用提供的InvocationHandler实例化代理类
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
// 如果代理对象无法正常创建,抛出内部错误
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
// 如果构造函数调用抛出异常,解包异常并处理
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
// 如果没有找到预期的构造函数,抛出内部错误
throw new InternalError(e.toString(), e);
}
}
// 其他省略的代码...
}
此方法的执行流程如下:
- 参数验证:验证传入的
InvocationHandler h
是否为null
。 - 安全权限检查(如果存在安全管理器):使用
checkProxyAccess
方法检查是否有创建代理实例的权限。 - 代理类的查找或生成:调用
getProxyClass0
方法来获取或生成相应的代理类。 - 调用代理类的构造函数创建实例:找到代理类的唯一构造函数(该构造函数以
InvocationHandler
为参数),并判断是否需要通过权限检查调用setAccessible
来允许访问非公共构造函数。 - 创建并返回代理实例:最后,通过反射调用构造器的
newInstance
方法,并将InvocationHandler h
传递为参数,创建代理对象的实例。
代理类$Proxy是什么样子
那么生成的proxyInstance对象到底是什么,为什么调用它的execute方法会执行MyInvocationHandler的invoke方法呢???
看到下面生成的代理对象的字节码文件,是不是一切都明白你了,原理竟然如此简单!
上面我们已经介绍了如何生成代理类的字节码到本地,打开进行查看
public final class $Proxy0 extends Proxy implements ServiceInterface {
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 void execute() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
.....省略toString/hashCode/equals等方法
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("com.useful.tool.ServiceInterface").getMethod("execute");
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
类,其主要目的是为了传递InvocationHandler
。 - 代理类实现了被代理的接口
ServiceInterface
,这也是为什么代理类可以直接强转成接口的原因。 - 有一个公开的构造函数,参数为指定的
InvocationHandler
,并将参数传递到父类Proxy
中。 - 每一个实现的方法,都会调用
InvocationHandler
中的invoke
方法,并将代理类本身、Method实例、入参三个参数进行传递。这也是为什么调用代理类中的方法时,总会分派到InvocationHandler
中的invoke
方法的原因。
注意的地方
public final void execute() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
上面的super
也就是我们的Proxy
类,super.h
也就是Proxy里面的InvocationHandler h
变量
protected InvocationHandler h;
不知道细心的小伙伴有没有注意到,为什么我们在debug的时候生成的代理类的变量总是一个h,我之前就一直很奇怪,直到这次深入了解后发现原来如此~~~
参考文章:
https://segmentfault.com/a/1190000039303463#item-3
https://blog.csdn.net/MrYushiwen/article/details/111473126