文章目录
- 前言
- 一、动态代理是什么?
-
- [1.1 动态代理是什么:](#1.1 动态代理是什么:)
- [1.2 动态代理都能干啥:](#1.2 动态代理都能干啥:)
- 二、静态代理VS动态代理:
-
- [2.1 静态代理](#2.1 静态代理)
- [2.2 动态代理](#2.2 动态代理)
- 三、动态代理的原理:
-
- [3.1 JDK 动态代理 原理](#3.1 JDK 动态代理 原理)
- [3.2 CGLIB 动态代理 原理](#3.2 CGLIB 动态代理 原理)
- [3.3 动态代理通用执行流程(两种都适用)](#3.3 动态代理通用执行流程(两种都适用))
- [四、spring中使用 jdk 动态代理目标类:](#四、spring中使用 jdk 动态代理目标类:)
-
- [4.1 spring中代理接口类:](#4.1 spring中代理接口类:)
- [4.2JDK 动态代理 完整流程图:](#4.2JDK 动态代理 完整流程图:)
- 总结
前言
本文介绍动态代理的原理以及实现。
一、动态代理是什么?
1.1 动态代理是什么:
如果说目标类:正经干活的员工,那么动态动态代理生成的类就是:前台门卫;所有人找员工办事,必须先过门卫;
门卫可以先登记、校验、分流、拦错,然后在找员工处理问题。以下为动态代理的过程
bash
控制器/业务调用方法
↓
走到【代理替身】
↓
进入拦截器(你写的invoke)
↓
前置增强(打印日志、路由、权限、限流)
↓
调用真实目标方法
↓
后置增强、统一异常处理
↓
返回结果
1.2 动态代理都能干啥:
- 统一异常包装
- 动态路由(根据参数选不同实现类)
- 统一日志、耗时统计
- 事务控制、权限校验
- Spring AOP 底层就是动态代理
- Feign、MyBatis Mapper 接口全是动态代理实现
二、静态代理VS动态代理:
2.1 静态代理
你手动写一个代理类,硬编码实现接口,包裹目标类,使用的时候先调用代理类,代理类中在调用目标类; 缺点:每一个接口都要手写代理类,类一多爆炸、重复代码多。
2.2 动态代理
不用手写代理类,程序运行期间自动帮你生成代理类 / 代理对象,通用一套拦截逻辑,能代理任意接口 / 类
三、动态代理的原理:
动态代理 = 程序运行期间,JVM 自动动态生成一个代理类字节码,加载到内存,创建代理对象;所有方法调用先走代理拦截器,再中转调用真实目标方法。实现方式有两种 JDK 动态代理 和 CGLIB 动态代理。
3.1 JDK 动态代理 原理
前提:必须有接口,只能代理接口。底层原理步骤:
- 运行时根据接口 Class 对象,自动在内存生成全新代理类字节码;
- 这个自动生成的代理类 实现了你要代理的接口;
- 代理类里重写了接口所有方法;
- 你调用接口任意方法,实际调用的是「自动生成代理类」里的方法;
- 代理类方法内部,固定回调你写的 InvocationHandler.invoke();
- 在 invoke 里你可以:前置增强、路由、判参数、抓异常、再反射调用真实实现类方法。
如果你有接口:
bash
interface UserService { sayHello(); }
JDK 自动生成:
bash
public final class $Proxy123 implements UserService {
InvocationHandler h;
@Override
public void sayHello(String name) {
// 直接回调你的拦截器
h.invoke(this, 方法对象, 参数数组);
}
}
你完全看不到这个 $Proxy123,JVM 内存里临时生成、临时加载。
3.2 CGLIB 动态代理 原理
前提:不需要接口,直接代理普通类。底层原理:
- 依靠字节码技术,运行时继承目标业务类,生成子类;
- 子类重写父类所有非 final 方法;
- 调用方法时先走子类重写的方法,进入 CGLIB 方法拦截器;
- 拦截器里做前置 / 后置 / 异常增强,再调用父类真实方法。
目标类:UserServiceImplCGLIB 动态生成:
bash
public class UserServiceImpl$$EnhancerByCGLIB extends UserServiceImpl {
// 重写所有方法,加入拦截逻辑
}
关键特点:
- 无需接口,直接代理普通类
- 基于 继承子类 做代理
- final 类 /final 方法无法被代理(不能继承、不能重写)
- 第三方字节码框架,Spring 已内置封装
3.3 动态代理通用执行流程(两种都适用)
- 程序运行中,代理工厂生成代理类字节码
- JVM 加载代理类,创建代理对象
- 开发者注入 / 使用代理对象
- 外部调用代理对象的方法
- 进入自定义拦截器(invoke)
- 拦截器内:
前置处理(日志、权限、路由)
反射调用真实目标方法
后置处理、统一异常转换 - 返回结果
四、spring中使用 jdk 动态代理目标类:
4.1 spring中代理接口类:
(1) 定义接口类:
bash
public interface UserService {
String sayHello(String name);
}
(2)定义真实目标实现类:
实现类1
bash
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public String sayHello(String name) {
System.out.println("业务逻辑:Hello " + name);
return "业务逻辑:Hello " + name;
}
}
实现类2
bash
import org.springframework.stereotype.Service;
@Service
public class UserServiceTwoImpl implements UserService {
@Override
public String sayHello(String name) {
System.out.println("业务逻辑 two:Hello " + name);
return "业务逻辑 two:Hello " + name;
}
}
(3)定义接口代理类:
bash
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserServiceProxy {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
@Autowired
@Qualifier("userServiceTwoImpl")
private UserService userTwoService;
@Bean
public UserService userProxyService() {
// 生成动态代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
(InvocationHandler) (p, method, args) -> {
System.out.println("正在执行方法:" + method.getClass().getName() + "," + method.getName());
System.out.println("代理前置:方法拦截增强");
// 执行真实业务方法:getService 获取真实的目标类
return method.invoke(getService(args), args);
}
);
return proxy;
}
private UserService getService(Object[] args) {
if (null == args || args.length == 0) {
return userService;
}
String userName = args[0].toString();
if ("twoUser".equals(userName)) {
return userTwoService;
} else {
return userService;
}
}
}
(4) 注入接口代理类完成接口调用:
bash
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/user")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping(value = "/getStudentName")
public String getStudentUserName(HttpServletRequest req) {
return studentService.getStudentName(req.getParameter("userName"));
}
}
4.2JDK 动态代理 完整流程图:
bash
┌─────────────────────────────────────────────────────────────┐
│ 调用方:Controller / 业务类 │
└───────────────┬─────────────────────────────────────────────┘
│ 调用 接口方法 UserService.sayHello()
▼
┌─────────────────────────────────────────────────────────────┐
│ JDK 自动生成【内存代理类 $ProxyXX】(实现 UserService 接口) │
│ 特点:运行时动态生成字节码,磁盘无文件,只在内存中 │
└───────────────┬─────────────────────────────────────────────┘
│ 代理类内部自动回调
▼
┌─────────────────────────────────────────────────────────────┐
│ 自定义 InvocationHandler 拦截器(你写的 Lambda 代理逻辑) │
│ 入口:invoke(proxy, method, args) │
│ 1. 前置增强:日志、路由、权限、打印 │
│ 2. 过滤 Object 方法(toString/equals 避免重复打印) │
└───────────────┬─────────────────────────────────────────────┘
│ 反射调用
▼
┌─────────────────────────────────────────────────────────────┐
│ 真实目标对象:UserServiceImpl 业务实现类 │
│ 执行真正业务逻辑 │
└───────────────┬─────────────────────────────────────────────┘
│ 方法返回结果
▼
┌─────────────────────────────────────────────────────────────┐
│ 原路逐层返回:实现类 → 拦截器 → 代理类 → 调用方 │
└─────────────────────────────────────────────────────────────┘
总结
- JDK 动态代理:实现接口、运行时生成接口实现类、只能代理接口
- CGLIB 动态代理:继承父类、字节码生成子类、可代理普通类
- 核心原理:运行时动态生成字节码 → 生成代理类 → 拦截所有方法 → 增强 + 中转调用真实方法
- Spring AOP、MyBatis Mapper、Feign 底层全靠动态代理实现。