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 UserServiceImplJDK就不知道该实现什么。
所以:
JDK动态代理 必须有接口九、没有接口怎么办?
这时候就轮到 CGLIB 出场了。
原理:
JDK动态代理: 实现接口 CGLIB: 继承目标类例如:
class UserServiceCGLIB生成:
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动态代理核心接口?
InvocationHandler4、JDK动态代理为什么必须有接口?
代理类通过实现接口生成。
没有接口无法实现。
5、Spring AOP使用什么代理?
有接口:JDK代理 无接口:CGLIB代理6、MyBatis为什么接口没有实现类还能调用?
因为底层使用动态代理生成Mapper对象。
总结
动态代理可以说是 Java 框架世界的基石。
很多框架看起来很神奇:
Spring AOP
Spring事务
MyBatis
OpenFeign
Dubbo
本质上都离不开动态代理。
静态代理是程序员自己写代理类,动态代理是 JVM 帮你生成代理类。
再记住动态代理的核心流程:
调用代理对象 ↓ 进入invoke() ↓ 执行增强逻辑 ↓ 调用目标对象 ↓ 返回结果当你真正理解之后,Spring AOP、事务、MyBatis 的底层原理也就打开了一半的大门。
