关注微信公众号 一只蜘蛛 及时获取原文+最新技术文章
本文会用一个业务场景——“下单并支付”,来讲清楚动态代理,顺便介绍其用到的反射机制以及高级的动态代理 AOP。
讲动态代理前,先说说什么是代理?为什么要有代理?
假设现在有一个大明星叫杨超越,它有唱歌和跳舞的本领,作为大明星是要用唱歌和跳舞来赚钱的,但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。
杨超越觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。
于是杨超越就找个一个经济公司,请了一个代理人,代理杨超越处理这些事情,如果有人想请杨超越演出,直接找代理人就可以了。如下图所示
我们说杨超越的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?
解决这个问题,Java 使用的是接口,杨超越想找代理,在 Java 中需要杨超越实现了一个接口,接口中规定要唱歌和跳舞的方法。Java 就可以通过这个接口为杨超越生成一个代理对象,只要接口中有的方法代理对象也会有。(可以理解为杨超越本身有个本子,上面记着自己会做的事情,代理人拿到这个本子自然就知道超越都会干啥,以及自己是一个怎么样的代理人-自己要代理啥)
下面会以这个为例,用具体的代码讲解动态代理。
动态代理?那什么是静态代理呢?
- 公共接口(明星的业务能力)
public interface Star {
void sing(String songName);
void dance(String danceName);
}
- 真实明星(业务实现人/类):杨超越
public class YangChaoYue implements Star {
@Override
public void sing(String songName) {
System.out.println("杨超越正在演唱《" + songName + "》");
}
@Override
public void dance(String danceName) {
System.out.println("杨超越正在表演《" + danceName + "》");
}
}
- 静态代理:助理“小静”
public class AssistantStaticProxy implements Star {
private final Star realStar; // 真正的杨超越
public AssistantStaticProxy(Star realStar) {
this.realStar = realStar;
}
@Override
public void sing(String songName) {
prepare("话筒");
collectMoney();
realStar.sing(songName); // 杨超越唱歌
afterWork();
}
@Override
public void dance(String danceName) {
prepare("场地");
collectMoney();
realStar.dance(danceName); // 杨超越跳舞
afterWork();
}
private void prepare(String item) {
System.out.println("[助理] 准备" + item);
}
private void collectMoney() {
System.out.println("[助理] 收钱");
}
private void afterWork() {
System.out.println("[助理] 演出结束,收尾款\n");
}
}
可以看到这个代理人实现了 超越姐的业务能力 接口(拿到了本子),且自己也和超越姐达成协议(构造方法获取了超越姐,这样之后就可以通知她要干什么),然后实现了 所有业务能力,且增加了要代理的内容(在超越姐唱歌前后办一些事情)。
说白了就是对 业务实现类的一个对象 中的方法进行二次封装。对象 体现在构造代理类时传入了真实的业务对象;二次封转体现在 同名方法内除了真实的业务对象方法调用外增加了其他功能。
- 代理人真实代理场景
public class StaticProxyShow {
public static void main(String[] args) {
// 超越姐准备就绪
Star yang = new YangChaoYue();
// 代理人也准备好了
// 这里用Star接收代理人,是因为无论超越姐还是代理人都是服务于Star这个业务能力接口的
// 在业务视角,代理人和超越姐是平级的
Star assistant = new AssistantStaticProxy(yang);
// 代理人收到观众点歌开始工作
assistant.sing("《卡路里》");
assistant.dance("创造101");
}
}
- 静态代理:公司给杨超越配了“小静”——一个只能服务杨超越的贴身助理,想再签王一博?得再招一个“小王”。
动态代理是怎么样的呢?
这里的初始明星业务与上面的一样,只是在代理人的设计上不一样了。
- 统一经纪人处理器(一个类代理所有明星)
public class AgentHandler implements InvocationHandler {
private final Object realStar; // 可以是杨超越,也可以是别人
public AgentHandler(Object realStar) {
this.realStar = realStar;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("sing".equals(methodName)) {
prepare("话筒");
} else if ("dance".equals(methodName)) {
prepare("场地");
}
collectMoney();
Object result = method.invoke(realStar, args); // 真正干活
afterWork();
return result;
}
private void prepare(String item) {
System.out.println("[经纪人] 准备" + item);
}
private void collectMoney() {
System.out.println("[经纪人] 收钱");
}
private void afterWork() {
System.out.println("[经纪人] 演出结束,收尾款\n");
}
}
- 动态代理人真实代理场景
public class DynamicProxyShow {
public static void main(String[] args) {
Star yang = new YangChaoYue();
Star agent = (Star) Proxy.newProxyInstance(
Star.class.getClassLoader(),
new Class[]{Star.class},
new AgentHandler(yang)
);
agent.sing("卡路里");
agent.dance("创造101");
}
}
我们再来深入理解一下动态代理的相关 API:
- 核心 API
public static Object newProxyInstance(
ClassLoader loader, // ① 类加载器
Class<?>[] interfaces, // ② 要实现的接口列表
InvocationHandler h // ③ 方法调用处理器
)
这里补充一下 ClassLoader 吧:
Star.class.getClassLoader()
就是 “让代理类跟 Star 接口在同一个户口本上”,保证运行时
agent instanceof Star
为 true,不会出现“认不得”的尴尬。
类加载器 ClassLoader:搬运工:负责把 。class 文件(或内存字节码)搬到 JVM 的方法区
类对象 。class:已经搬进来的那份“图纸”
getClassLoader():问这张图纸:“当初是哪位搬运工把你搬进来的?”
为什么代理类要用 同一位搬运工?
安全限制
Java 的类加载器有“双亲委派”机制,不同搬运工之间是隔离的。
如果代理类用了一个跟Star
不一样的搬运工,运行时就会抛出
ClassCastException: $Proxy0 cannot be cast to Star
。可见性
只有同一个搬运工(或它的父搬运工)加载的类,才能互相看见。
否则代理类虽然实现了Star
,但 JVM 认为那是“两个不同的 Star”。
运行时到底发生了什么?(流程拆解)
- JVM 在内存中 动态生成一个类
$Proxy0 implements Star
伪代码大致长这样:
// 这段代码不是手写的,是 JVM 在内存里拼出来然后加载的
public final class $Proxy0 extends Proxy implements Star {
// 方法句柄,缓存起来省得每次都反射查找
private static Method m1; // sing
private static Method m2; // dance
// 构造函数:把 InvocationHandler 传进去
public $Proxy0(InvocationHandler h) {
super(h); // ← 把 AgentHandler 赋给 Proxy.h
}
@Override
public void sing(String songName) {
super.h.invoke(this, m1, new Object[]{songName});
}
@Override
public void dance(String danceName) {
super.h.invoke(this, m2, new Object[]{danceName});
}
}
-
把
$Proxy0
的字节码通过loader
加载到 JVM -
通过反射创建
$Proxy0
实例,并把AgentHandler
传进去 -
以后任何
agent.sing(...)
都会触发AgentHandler.invoke(...)
概括一下:
-
静态代理:公司每签一个明星就招一个“小静”“小王”——类爆炸。
-
动态代理:公司培训了一个 万能经纪人模板(
InvocationHandler
),
通过 Proxy.newProxyInstance 在 运行时 把模板“打印”成任何明星的专属经纪人,
既省人力,又能随时加新招式(rap、篮球)而不用改现有代码。
基于上文的动态代理,我们再来看看 JDK 的反射机制
哪里用到了反射?
反射的底层流程是怎么样的?
- 用户态 → JVM 入口
// 你写的代码
method.invoke(realStar, "卡路里");
-
这行代码在 Java 层 只是普通调用;
-
真正干活的是 native 方法
Method.invoke0(...)
。
- 进入 HotSpot 源码(C++ 层)
JavaCalls::call()
└─ Reflection::invoke_method()
├─ 1. 权限检查(public?private?)
├─ 2. 包装参数(把 Object[] 转成 C++ 对应类型)
├─ 3. 找到真正的 **目标方法指针**(Method*)
├─ 4. 选择调用方式
│ ├─ 4.1 如果已经 **JIT 编译过** → 直接跳转到 **编译后的机器码**
│ └─ 4.2 否则 → 走 **解释器** 的入口 stub
└─ 5. 执行完成后,把返回值再包回 Java 对象
- 关键数据结构
JIT 优化:反射为什么越来越快?
-
第 1 次 调用:走 JNI + 解释器,确实慢。
-
第 N 次 调用:HotSpot 会生成 动态反射手柄(
MethodAccessor
的 ASM 版本),
把method.invoke
直接 内联成普通 invokevirtual,性能接近 直接调用(差距 < 2 倍)。
一张图总结反射底层流程
Java 层
method.invoke(realStar, args)
│
▼
JNI 入口 Method.invoke0()
│
▼
HotSpot C++
Reflection::invoke_method()
├─ 权限检查
├─ 参数解包
├─ 找 Method* → vtable/itable 定位
├─ 选择解释器 or JIT 代码
└─ 执行 & 返回
总结概括一下
反射机制是 JVM 在运行时借助 Class 元数据(运行时常量池、方法表、字段表等) 提供的自省(introspection)能力:
通过 java.lang.Class
及 java.lang.reflect.*
API,先 定位并解析 目标类的 Class
对象,再 按需获取 其 Method
、Field
、Constructor
等元数据对象,最后 绕过编译期绑定,在 解释器或 JIT 生成的适配 stub 中动态完成成员访问或方法调用。
在动态代理场景下,这一过程使框架能够在 运行期 为接口生成 $Proxy0
代理类,并将接口方法统一路由到用户提供的 InvocationHandler
,实现横切逻辑的织入。
AOP 是什么?他与动态代理有什么关系?
AOP 就是把“与业务无关、却在各处重复出现的横切逻辑”抽出来,做成一个可插拔的“切面”,然后在 运行期 通过动态代理(+反射)织入到目标方法的前后、环绕或异常返回点。
想象一个场景:
- 订单服务、支付服务、库存服务……每个方法都要写:
– 记录日志
– 开启 / 提交事务
– 监控耗时
– 权限校验
这些代码 一模一样,却 侵入 在每个业务类里 → 维护噩梦。
AOP 把这些“横切关注点”统一收编到一个 切面(Aspect) 里,业务类保持纯粹。
核心概念(对照动态代理秒懂)
AOP 术语 | 动态代理里的对应物 | 解释 |
---|---|---|
Join Point | 每一个可能被拦截的方法调用 | 所有 method.invoke() 的点 |
Pointcut | 用表达式筛出来的那一批方法 | 比如 execution(* com.xxx.service..*(..)) |
Advice | InvocationHandler.invoke() 里写的逻辑 | @Before、@After、@Around 等 |
Aspect | 把 Pointcut + Advice 打包成一个类 | 你写的 @Aspect 注解类 |
Weaving | 把代理对象塞给调用方 | Spring 启动时或运行时完成 |
Spring AOP 的实现套路(最常用)
-
有接口 → 默认用 JDK 动态代理
-
生成
$Proxy
类,实现业务接口。 -
调用链路:
@Autowired OrderService
→ 注入的是代理 → 代理内部method.invoke()
前后执行 Advice。
-
-
无接口 / 指定 CGLIB → 用 CGLIB 字节码增强
-
生成目标类的子类(
OrderService$$EnhancerBySpringCGLIB$$...
)。 -
通过 MethodInterceptor(类似
InvocationHandler
)织入逻辑。
-
-
Weaving 时机
-
Spring 容器启动时,通过
BeanPostProcessor
把原始 bean 包一层代理再返回。 -
所以 单例 bean 在容器里存的就是代理对象。
-
代码 30 秒速览
@Aspect
@Component
public class LogAspect {
// 1. 定义切点:所有 service 包下的 public 方法
@Pointcut("execution(public * com.demo.service..*(..))")
public void serviceMethods(){}
// 2. 定义 Advice:环绕
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed(); // 相当于 method.invoke(...)
} finally {
long cost = (System.nanoTime() - start) / 1_000_000;
log.info("{} took {} ms", pjp.getSignature(), cost);
}
}
}
-
业务类
OrderService
干干净净,没有任何日志代码。 -
Spring 启动时自动生成代理,把
logAround
织进去。
与动态代理 / 反射的关系总结
-
底层机制 就是动态代理(JDK 或 CGLIB),反射负责拿到
Method
并调用。 -
AOP 框架 帮你把“写代理类、写
InvocationHandler
、缓存 Method、处理异常”这些脏活累活全包圆了。 -
最终效果:业务代码专注业务,横切逻辑集中管理,可插拔、可复用、可测试。
所以说,在运行时 动态代理利用反射机制获取类的元数据生成代理类$Proxy,而 AOP 则是对动态代理的进一步封装