三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Java动态代理详解:小白也能彻底搞懂动态代理!

Java动态代理详解:小白也能彻底搞懂动态代理!

🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏:数据结构与算法,JavaSE ,苍穹外卖日记,AI学习

前言

在学习 Spring 的时候,你一定听过这样一句话:

Spring AOP 的底层就是动态代理

很多同学第一次看到动态代理时都会懵:

  • 什么是代理?

  • 为什么需要代理?

  • 动态代理和静态代理有什么区别?

  • InvocationHandler 又是什么东西?

  • Spring 为什么离不开动态代理?

别慌。今天我们就用最通俗的方式,把 Java 动态代理讲明白。

看完这篇文章,你不仅能写出动态代理代码,还能理解 Spring AOP 为什么能够在方法执行前后自动增强功能。


一、什么是代理?

在讲动态代理之前,我们先理解什么是代理。

生活中其实到处都是代理。

举个例子:

假设你想买房。

正常流程:

买家 → 房东

但是现实中很多人不会直接找房东,而是找中介:

买家 → 中介 → 房东

中介就是代理。

它帮你完成:

  • 带看房

  • 签合同

  • 收取佣金

但真正卖房的还是房东。


对应到程序中,假设有一个用户服务:

public interface UserService { void login(); }

实现类:

public class UserServiceImpl implements UserService { @Override public void login() { System.out.println("用户登录..."); } }

如果我们想在登录前后增加日志:

记录日志 ↓ 执行登录 ↓ 记录日志

怎么办?

直接修改源代码?

public void login() { System.out.println("记录日志"); System.out.println("用户登录"); System.out.println("记录日志"); }

可以,但不优雅。

因为业务代码和日志代码耦合在一起了。

于是代理模式出现了。


二、什么是静态代理?

静态代理很好理解。

代理类提前写好。

创建代理类

public class UserServiceProxy implements UserService { private UserService userService; public UserServiceProxy(UserService userService) { this.userService = userService; } @Override public void login() { System.out.println("记录日志"); userService.login(); System.out.println("记录日志"); } }

测试:

public class Test { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.login(); } }

输出:

记录日志 用户登录... 记录日志

静态代理的问题

假设系统有:

UserService OrderService ProductService

每个都要写代理类:

UserServiceProxy OrderServiceProxy ProductServiceProxy

项目越大:

代理类越来越多 维护成本越来越高

怎么办?

Java 提供了动态代理


三、什么是动态代理?

你可以理解成:

代理对象不需要程序员手动创建,而是在运行时动态生成。

说白了:

静态代理: 程序员自己写代理类 动态代理: JVM帮你生成代理类

这就是动态代理。


四、动态代理核心组成

JDK动态代理主要有两个核心类:

Proxy

负责生成代理对象。

Proxy.newProxyInstance()

InvocationHandler

负责拦截方法。

invoke()

所有方法调用最终都会来到这里。

你可以理解成:

客户 ↓ 代理对象 ↓ invoke() ↓ 目标对象

五、动态代理工作流程

假设执行:

proxy.login();

整个过程如下:

调用代理对象 ↓ 进入invoke() ↓ 执行前置增强 ↓ 调用真实对象 ↓ 执行后置增强 ↓ 返回结果

流程图:

客户端 ↓ 代理对象 ↓ InvocationHandler ↓ 目标对象

这就是 Spring AOP 的核心思想。


六、JDK动态代理代码实战

第一步:定义接口

public interface UserService { void login(); void register(); }

第二步:实现类

public class UserServiceImpl implements UserService { @Override public void login() { System.out.println("用户登录"); } @Override public void register() { System.out.println("用户注册"); } }

第三步:创建代理处理器

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class LogHandler implements InvocationHandler { //目标对象 private Object target; public LogHandler(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; } }

第四步:生成代理对象

import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass() .getClassLoader(), target.getClass() .getInterfaces(), new LogHandler(target) ); proxy.login(); System.out.println("------------"); proxy.register(); } }

运行结果:

方法执行前记录日志 用户登录 方法执行后记录日志 ------------ 方法执行前记录日志 用户注册 方法执行后记录日志

成功实现了功能增强,并且没有修改业务代码


七、Proxy.newProxyInstance详解

很多同学最怕这一行:

Proxy.newProxyInstance(...)

其实并不复杂。

Proxy.newProxyInstance( classLoader, interfaces, handler )

参数1:类加载器

target.getClass().getClassLoader()

用于加载代理类。

一般直接这么写即可。


参数2:接口数组

target.getClass().getInterfaces()

告诉 JVM:

我要代理哪些接口

参数3:InvocationHandler

new LogHandler(target)

方法增强逻辑写在这里。


八、为什么JDK动态代理必须有接口?

很多面试官喜欢问:

JDK动态代理为什么要求目标对象实现接口?

原因很简单。

JDK生成的代理类本质上:

class ProxyClass implements UserService

代理类通过实现接口完成代理。

如果没有接口:

class UserServiceImpl

JDK就不知道该实现什么。

所以:

JDK动态代理 必须有接口

九、没有接口怎么办?

这时候就轮到 CGLIB 出场了。

原理:

JDK动态代理: 实现接口 CGLIB: 继承目标类

例如:

class UserService

CGLIB生成:

class UserServiceProxy extends UserService

这样即使没有接口也能代理。


Spring AOP 的规则:

有接口 优先JDK动态代理 无接口 使用CGLIB

十、动态代理的底层原理

很多人以为:

Proxy.newProxyInstance()

只是返回一个对象。

其实背后 JVM 做了很多事。

大致流程:

生成字节码 ↓ 生成代理类 ↓ 加载到JVM ↓ 创建对象 ↓ 返回代理实例

本质上:

动态生成一个新的class文件

只不过这些过程 JVM 帮我们完成了。


十一、动态代理的优缺点

优点

1. 解耦

业务代码不需要关心日志。

业务 日志 权限 事务

各自独立。


2. 代码复用

一个代理可以增强多个对象。


3. 扩展方便

增加新功能不用修改源码。

符合:

开闭原则

缺点

1. 增加理解成本

初学者看不懂。


2. 调试困难

调用链变长。


3. 性能略有损耗

因为多了一层代理。

不过现代 JVM 优化后影响非常小。


十二、实际开发中的应用

动态代理在企业项目中非常常见。


Spring AOP

@Before @After @Around

底层就是动态代理。


Spring事务

@Transactional

本质也是代理。

执行流程:

开启事务 ↓ 执行方法 ↓ 提交事务

MyBatis

Mapper接口:

UserMapper

实际上没有实现类。

为什么还能调用?

userMapper.selectById()

因为 MyBatis 底层动态生成了代理对象。


RPC框架

例如:

Dubbo OpenFeign

底层都大量使用动态代理。


十三、扩展

1、什么是动态代理?

运行期间动态生成代理对象,实现方法增强。


2、动态代理有哪些实现方式?

JDK动态代理 CGLIB动态代理

3、JDK动态代理核心接口?

InvocationHandler

4、JDK动态代理为什么必须有接口?

代理类通过实现接口生成。

没有接口无法实现。


5、Spring AOP使用什么代理?

有接口:JDK代理 无接口:CGLIB代理

6、MyBatis为什么接口没有实现类还能调用?

因为底层使用动态代理生成Mapper对象。


总结

动态代理可以说是 Java 框架世界的基石。

很多框架看起来很神奇:

  • Spring AOP

  • Spring事务

  • MyBatis

  • OpenFeign

  • Dubbo

本质上都离不开动态代理。

静态代理是程序员自己写代理类,动态代理是 JVM 帮你生成代理类。

再记住动态代理的核心流程:

调用代理对象 ↓ 进入invoke() ↓ 执行增强逻辑 ↓ 调用目标对象 ↓ 返回结果

当你真正理解之后,Spring AOP、事务、MyBatis 的底层原理也就打开了一半的大门。

← 返回列表