1. 什么是“代理”?(偷梁换柱)
在 Spring 容器里,你写了一个 EmpServiceImpl(大明星)。
但是,当你去 Controller 里注入 EmpService 的时候,Spring 并没有把那个真正的 EmpServiceImpl 给你,而是偷偷调包了。
它给你的是一个“假冒的替身”(代理对象)。
- 你的视角:你看着这个替身长得跟大明星一模一样(因为它们实现了同一个接口),所以你以为你调用的就是大明星。
- 实际情况:你调用的其实是替身。
2. 什么是“动态”?(现场打印名片)
- 静态代理:为了搞个替身,你还得专门写一个
Agent.java类,编译成.class文件。如果有 100 个明星,你就得写 100 个经纪人类,累死。 - 动态代理:牛就牛在这里! 程序运行的时候,JDK 或者 CGLIB(一个代码生成库)会在内存里,“刷”地一下,现场给你生成一个替身类的字节码。你不用写代码,这个替身是自动变出来的。
3. 动态代理是怎么干活的?(中间商赚差价)
当你在 Controller 里调用 empService.deleteEmp() 时,实际发生的流程是这样的:
- 拦截:你调用了替身(代理对象)的方法。
- 加料(前置通知) :替身先执行了 AOP 里定义的切面逻辑(比如:
System.out.println("开始记录日志..."))。 - 甩锅(反射调用) :替身通过 Java 的反射机制(Reflection),去调用真正的大明星(目标对象)的
deleteEmp()方法。 - 加料(后置通知) :大明星干完活,替身再执行剩下的切面逻辑。
- 交差:替身把大明星的返回结果,还给你。
4. 两种主流流派(JDK vs CGLIB)
Spring 底层会自动在以下两种方式中切换,面试常考:
第一派:JDK 动态代理(官方正版)
- 条件:大明星必须有“工牌” (必须实现 Interface 接口)。
- 原理:JDK 会在运行时生成一个类,这个类也戴着同样的“工牌”(实现了同样的接口)。
- 局限:如果你的类没有实现接口,JDK 就傻眼了,干不了。
第二派:CGLIB 动态代理(民间高手)
- 条件:大明星没有“工牌” (只是一个普通的类,没实现接口)。
- 原理:认干爹。CGLIB 会在运行时生成一个类,它是大明星的子类(继承了大明星)。
- 局限:因为是继承,所以如果大明星被
final修饰了(绝后了),CGLIB 就没法生成子类了。
总结
- 代理:就是 Spring 给你发了个“高仿货”。
- 动态:这个高仿货是程序运行通过字节码技术自动生成的,不用你手动写。
- 作用:你调高仿货的方法,它帮你干完杂活(日志、事务)后,再去找真货干正事。