代理机制
前言
了解一个知识点的真正底层。不仅仅去聚焦于技术实现,要去仔细思考,为什么会有这个东西出现?他的作用是什么?我们要锻炼这种思维能力,任何一种技术的关键在于其实现的底层逻辑,而不是用什么计算机语言和工具去实现。
本文涉及知识点
- 代理
- 静态代理
- 动态代理
- JDK动态代理
- CGLIB动态代理
初识代理
当面试官问出:"你知道Java中代理吗?"。身为面试者,猛然心中一惊:完了完了,平时就写写业务代码,也没用到代理。仔细想了想,不对,设计模式中好像有一个代理模式,嗯,对,java好像还有动态代理机制,然后就开始巴拉巴拉一顿输出。30s后,你看着面试官,希望从面试官脸上看出肯定的表情,然而此时的面试官心里:"小伙汁儿,八股文背的不错"。 不幸的是,此次面试也宣布凉凉。 回去的路上,只恨只平时怎么没有多积累相关的知识点。
1. 什么是代理?

大白话解释:代理就是找个靠谱的中间人帮你干活
想象你叫外卖的场景:
- 你自己点餐 → 你直接打电话给餐厅,告诉老板你要什么(这是直接操作)。
- 你用外卖APP点餐 → APP帮你联系餐厅、送餐员,你只和APP沟通(这个APP就是代理)。
现实中的代理例子:
- 租房中介:你通过中介找房子,不用自己联系房东。
- 明星经纪人:明星不直接谈合作,由经纪人代理谈判。
- 科学上网工具:它代理你的网络请求,帮你访问境外网站(比如用VPN)。
代理的核心逻辑
- 中间商:代理就是帮你跑腿的中间人,你通过它间接完成事情。
- 隐藏的细节:你不用关心餐厅在哪、骑手怎么送,代理帮你处理复杂步骤。
- 功能增强:比如父母用「家长控制代理」过滤孩子看的网站(允许或拦截内容)。
2. Java中的代理
现在我们已经知道了代理是什么,是干什么的,那我们在返回来看一下在Java中的代理。请记住了代理的底层逻辑是不变的,就是中间人帮我们干活。类比到代码中就是,Java的代理类就是这个中间人。
还是用之前买奶茶的场景案例:
- 你自己去买 → 直接走到奶茶店下单(相当于直接调用对象的方法)。
- 找代购小哥帮你买- → 你告诉代购要什么,他替你去买,甚至可能偷偷加料(比如加珍珠或糖)。这个代购就是代理!
先看一段买奶茶的代码实现:
typescript
// 你:想喝奶茶的人
interface 奶茶店 {
void 买奶茶();
}
// 真实的奶茶店
class 真的奶茶店 implements 奶茶店 {
public void 买奶茶() {
System.out.println("店员:做好了一杯奶茶!");
}
}
// 代购小哥(静态代理)
class 代购小哥 implements 奶茶店 {
private 奶茶店 真店; // 持有一个真实对象的引用
public 代购小哥(奶茶店 真店) {
this.真店 = 真店;
}
public void 买奶茶() {
System.out.println("代购:跑腿费加5元!"); // **代理的附加操作**
真店.买奶茶(); // 调用真实对象的方法
System.out.println("代购:偷偷加了珍珠!"); // **代理的附加操作**
}
}
// 使用
public class Main {
public static void main(String[] args) {
奶茶店 真店 = new 真的奶茶店();
奶茶店 代理 = new 代购小哥(真店);
代理.买奶茶(); // 实际是代购小哥在干活
}
}
上述代码。有以下特点
- 代理类(代购小哥)需要手动编写,每个方法都要自己调用。
- 如果接口新增方法(比如买咖啡),代理类也得跟着改,麻烦!
当你熟读上面的代码时,恭喜你,你已经见到了Java代理中的静态代理,显而易见,在特点出能看到静态代理类的局限性,"如果新增方法,代理类也要跟着改。"
再看一段代码:
typescript
import java.lang.reflect.*;
// 动态代理的"跑腿APP"(自动生成代理类)
class 跑腿APP {
public static Object 下单(Object 真实对象) {
return Proxy.newProxyInstance(
真实对象.getClass().getClassLoader(),
真实对象.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("APP:收取跑腿费10元!"); // **统一附加操作**
Object result = method.invoke(真实对象, args); // 调用真实方法
System.out.println("APP:偷偷塞了小饼干!"); // **统一附加操作**
return result;
}
}
);
}
}
// 使用
public class Main {
public static void main(String[] args) {
奶茶店 真店 = new 真的奶茶店();
奶茶店 代理 = (奶茶店) 跑腿APP.下单(真店); // 动态生成代理对象
代理.买奶茶(); // 自动走InvocationHandler的逻辑
}
}
上述代码有以下特点:
- 运行时自动生成代理类(不用手动写代购小哥类)。
- 无论接口加多少方法(买奶茶、买咖啡),统一由InvocationHandler处理,省事!
- 背后用到了反射(Method.invoke)
当时你熟读以上代码的时候,恭喜你,已经学会了Java代理中的动态代理实现,以及其相关特点。
3. 代码中的代理使用
经过上面两段代码,相信你已经知道了Java中的静态代理和动态代理机制,现在开始,我们要结合实际的项目中,来看下代理在我们的代码中究竟扮演了什么角色和功能。
1. 中介代劳(隐藏复杂操作)
如果你是一名开发人员,我相信,你已经写过这种代码
scss
userService.save()
没错,见名知意,它的作用就是保存用户信息。在实际代码中可能我们只针对业务代码编写,但是你有没有想过。为什么我们只调用了save()就能保存我们的用户? 实际上我们运行这段代码的时候,就是告诉代理类(中间人)去帮助我们实现这个功能。
markdown
// 你调用userService.save(),代理背后帮你:
1. 打开数据库连接
2. 处理事务
3. 关闭连接
2. 加料控制(功能增强)
如果你不仅仅的只是想执行一下保存用户,你或许还想再执行前记录一下日志,或者进行一些权限校验,或许在你的方法前会有种注解。
less
@@Log(title = "", businessType = BusinessType.INSERT)
@PreAuthorize(hasAnyPermi = "list")
这个注解对应记录的是日志记录,权限检验。通过这种注解的方式,既不会对我们的代码进行侵扰,同时也提高了功能的可利用性。没错,这个就是Spring中的AOP功能(本文不做解释)。这些功能都是通过代理实现,就像外卖小哥偷偷给你加份薯条(无侵入式增强)(手动滑稽)。
上述的内容中,我们已经完全理解了代理的作用是啥,也分析了静态代理和动态代理的特点,下面我们将着重说下动态代理的使用。
4. 动态代理
JDK动态代理
在上文中,一直在陈述代理类作为一个中间人,帮我们执行我们需要的功能,也就是执行我们的代码。Java的标准库中就存在这样一个"中间人", 在JDK的原生包中(Proxy、InvocationHandler),运行时动态生成接口的实现类。为了更好的说明动态代理的实现逻辑,接下来,我还是会用奶茶的案例来进行阐述。
🧋场景还原
-
你是一个想喝奶茶的顾客(调用方)
-
奶茶店是真实做奶茶的(真实对象)
-
外卖小哥是帮你跑腿的(代理对象)
-
你和奶茶店之间必须有个外卖协议(接口) 外卖小哥才能按协议跑腿
-
JDK动态代理就像这个外卖平台,自动生成"外卖小哥"
第一步:定义「奶茶店接口」(外卖协议)
csharp
interface 奶茶店 {
void 制作奶茶(); // 协议里规定:奶茶店必须能做奶茶
}
- 就像美团要求商家必须上传「商品菜单」,外卖小哥才知道能帮你买什么。
第二步:真实的「奶茶店」
csharp
class 真实奶茶店 implements 奶茶店 {
public void 制作奶茶() {
System.out.println("【店员】奶茶做好了!");
}
}
- 真实奶茶店必须实现接口(签了外卖协议)
- 如果它不实现接口,外卖平台(JDK代理)不接单!
第三步:创建「外卖小哥」(代理对象)
arduino
public class 顾客 {
public static void main(String[] args) {
// 1. 先找到一家真实的奶茶店
奶茶店 真实对象 = new 真实奶茶店();
// 2. 通过JDK外卖平台,找个外卖小哥
奶茶店 代理 = (奶茶店) Proxy.newProxyInstance(
奶茶店.class.getClassLoader(), // 用奶茶店的协议标准
new Class[]{奶茶店.class}, // 外卖小哥只接奶茶店的单
(proxy, method, args) -> { // 外卖小哥的接单流程
System.out.println("【外卖小哥】接到订单");
Object result = method.invoke(真实对象, args); // 去奶茶店取货
System.out.println("【外卖小哥】跑腿费加5元");
return result;
}
);
// 3. 你告诉外卖小哥:我要奶茶!
代理.制作奶茶();
}
}
执行流程:

看到这你可能还是比较疑惑,这和静态代理,没有什么区别呀,不都是生成一个代理类吗,如果你有这种疑惑,请往下看。
现在场景升级:
- 假设你有 3家店铺(奶茶店、炸鸡店、蛋糕店),你想让一个"私人秘书(中间人)"帮你跑腿买这些东西
- 定义接口(店铺协议)
csharp
// 1. 奶茶店接口
interface 奶茶店 {
void 制作奶茶();
}
// 2. 炸鸡店接口
interface 炸鸡店 {
void 炸鸡();
}
// 3. 蛋糕店接口
interface 蛋糕店 {
void 做蛋糕();
}
- 真实店铺(实现类)
typescript
// 1. 真实奶茶店
class 真实奶茶店 implements 奶茶店 {
@Override
public void 制作奶茶() {
System.out.println("【奶茶店】奶茶做好了!");
}
}
// 2. 真实炸鸡店
class 真实炸鸡店 implements 炸鸡店 {
@Override
public void 炸鸡() {
System.out.println("【炸鸡店】炸鸡做好了!");
}
}
// 3. 真实蛋糕店
class 真实蛋糕店 implements 蛋糕店 {
@Override
public void 做蛋糕() {
System.out.println("【蛋糕店】蛋糕做好了!");
}
}
- 静态代理:私人秘书(手动编写代理类)
kotlin
// 1. 奶茶店秘书(代理类)
class 奶茶店秘书 implements 奶茶店 {
private 奶茶店 真实店铺;
public 奶茶店秘书(奶茶店 真实店铺) {
this.真实店铺 = 真实店铺;
}
@Override
public void 制作奶茶() {
System.out.println("【秘书】接到奶茶订单");
真实店铺.制作奶茶();
System.out.println("【秘书】跑腿费加5元");
}
}
// 2. 炸鸡店秘书(代理类)
class 炸鸡店秘书 implements 炸鸡店 {
private 炸鸡店 真实店铺;
public 炸鸡店秘书(炸鸡店 真实店铺) {
this.真实店铺 = 真实店铺;
}
@Override
public void 炸鸡() {
System.out.println("【秘书】接到炸鸡订单");
真实店铺.炸鸡();
System.out.println("【秘书】跑腿费加8元");
}
}
// 3. 蛋糕店秘书(代理类)
class 蛋糕店秘书 implements 蛋糕店 {
private 蛋糕店 真实店铺;
public 蛋糕店秘书(蛋糕店 真实店铺) {
this.真实店铺 = 真实店铺;
}
@Override
public void 做蛋糕() {
System.out.println("【秘书】接到蛋糕订单");
真实店铺.做蛋糕();
System.out.println("【秘书】跑腿费加10元");
}
}
- 使用静态代理
typescript
public class 静态代理Demo {
public static void main(String[] args) {
// 1. 创建真实店铺
奶茶店 奶茶店铺 = new 真实奶茶店();
炸鸡店 炸鸡店铺 = new 真实炸鸡店();
蛋糕店 蛋糕店铺 = new 真实蛋糕店();
// 2. 创建代理(私人秘书)
奶茶店 奶茶秘书 = new 奶茶店秘书(奶茶店铺);
炸鸡店 炸鸡秘书 = new 炸鸡店秘书(炸鸡店铺);
蛋糕店 蛋糕秘书 = new 蛋糕店秘书(蛋糕店铺);
// 3. 让秘书跑腿
奶茶秘书.制作奶茶();
炸鸡秘书.炸鸡();
蛋糕秘书.做蛋糕();
}
}
可以看下上面举例的静态代理的例子,有以下的缺点:
- 每代理一个店铺,就要写一个代理类
- 每新增一个店铺(比如咖啡店),就要写一个咖啡店秘书类
- 如果奶茶店新增一个方法(比如制作珍珠奶茶),奶茶店秘书也要跟着改
如果我们使用的是动态代理:
- 定义接口(店铺协议)
csharp
// 1. 奶茶店接口
interface 奶茶店 {
void 制作奶茶();
}
// 2. 炸鸡店接口
interface 炸鸡店 {
void 炸鸡();
}
// 3. 蛋糕店接口
interface 蛋糕店 {
void 做蛋糕();
}
- 真实店铺(实现类)
typescript
// 1. 真实奶茶店
class 真实奶茶店 implements 奶茶店 {
@Override
public void 制作奶茶() {
System.out.println("【奶茶店】奶茶做好了!");
}
}
// 2. 真实炸鸡店
class 真实炸鸡店 implements 炸鸡店 {
@Override
public void 炸鸡() {
System.out.println("【炸鸡店】炸鸡做好了!");
}
}
// 3. 真实蛋糕店
class 真实蛋糕店 implements 蛋糕店 {
@Override
public void 做蛋糕() {
System.out.println("【蛋糕店】蛋糕做好了!");
}
}
- 动态代理:美团外卖(自动适配所有店铺)
typescript
import java.lang.reflect.*;
public class 动态代理Demo {
public static void main(String[] args) {
// 1. 创建真实店铺
奶茶店 奶茶店铺 = new 真实奶茶店();
炸鸡店 炸鸡店铺 = new 真实炸鸡店();
蛋糕店 蛋糕店铺 = new 真实蛋糕店();
// 2. 创建动态代理(美团外卖)
奶茶店 奶茶代理 = (奶茶店) Proxy.newProxyInstance(
奶茶店.class.getClassLoader(),
new Class[]{奶茶店.class},
new 美团骑手(奶茶店铺)
);
炸鸡店 炸鸡代理 = (炸鸡店) Proxy.newProxyInstance(
炸鸡店.class.getClassLoader(),
new Class[]{炸鸡店.class},
new 美团骑手(炸鸡店铺)
);
蛋糕店 蛋糕代理 = (蛋糕店) Proxy.newProxyInstance(
蛋糕店.class.getClassLoader(),
new Class[]{蛋糕店.class},
new 美团骑手(蛋糕店铺)
);
// 3. 通过代理调用
奶茶代理.制作奶茶();
炸鸡代理.炸鸡();
蛋糕代理.做蛋糕();
}
}
// 4. InvocationHandler(美团骑手,统一处理所有订单)
class 美团骑手 implements InvocationHandler {
private Object 真实店铺;
public 美团骑手(Object 真实店铺) {
this.真实店铺 = 真实店铺;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【美团骑手】接到订单:" + method.getName());
Object result = method.invoke(真实店铺, args); // 去店铺取货
System.out.println("【美团骑手】配送完成,收取跑腿费");
return result;
}
}
通过上买的例子我们可以看到:
- 静态代理:每代理一个店铺(如奶茶店、炸鸡店),就要手动写一个代理类(如奶茶店秘书、炸鸡店秘书),代码冗余,难以维护。
- 动态代理:自动生成代理类,无论多少店铺,只需一个InvocationHandler,代码更简洁、扩展性强。
用一个表格总结一下:
下面我们抛出来一个很有意思的问题,为什么你上面的动态代理必须要有接口的设计?
根本原因:Java的单继承限制
- 所有JDK动态代理类都继承Proxy 查看反编译的代理类:
scala
public final class $Proxy0 extends Proxy implements 你的接口 {
// 自动生成的方法全部走InvocationHandler
}
- 由于Java禁止多继承,代理类已经继承了Proxy,无法再继承其他类
- 只能通过实现接口的方式扩展功能
生活比喻
- 想象Proxy是一个万能插座基座:
- 它已经占用了你家的唯一插座位(继承位)
- 你只能通过外接插线板(实现接口)扩展功能
CGLIB 动态代理
在日常的开发中,除了JDK自带的动态代理类,还有就是CGLIB的动态代理类,这个是Spring整合的第三方库,还是用奶茶店的例子来阐述。
- 真实店铺(没有接口!)
csharp
// 1. 真实奶茶店(没有实现任何接口!)
class 真实奶茶店 {
public void 制作奶茶() {
System.out.println("【奶茶店】奶茶做好了!");
}
}
// 2. 真实炸鸡店(也没有接口!)
class 真实炸鸡店 {
public void 炸鸡() {
System.out.println("【炸鸡店】炸鸡做好了!");
}
}
- CGLIB动态代理(闪送跑腿)
typescript
import net.sf.cglib.proxy.*;
public class CGLIB代理Demo {
public static void main(String[] args) {
// 1. 创建真实店铺
真实奶茶店 奶茶店铺 = new 真实奶茶店();
真实炸鸡店 炸鸡店铺 = new 真实炸鸡店();
// 2. 创建CGLIB代理(闪送跑腿)
真实奶茶店 奶茶代理 = (真实奶茶店) Enhancer.create(
真实奶茶店.class, // 代理哪个类
new 闪送跑腿(奶茶店铺) // 统一处理逻辑
);
真实炸鸡店 炸鸡代理 = (真实炸鸡店) Enhancer.create(
真实炸鸡店.class,
new 闪送跑腿(炸鸡店铺)
);
// 3. 通过代理调用
奶茶代理.制作奶茶();
炸鸡代理.炸鸡();
}
}
// 4. MethodInterceptor(闪送跑腿小哥)
class 闪送跑腿 implements MethodInterceptor {
private Object 真实店铺;
public 闪送跑腿(Object 真实店铺) {
this.真实店铺 = 真实店铺;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【闪送】接到订单:" + method.getName());
Object result = proxy.invokeSuper(obj, args); // 直接调用父类方法
System.out.println("【闪送】跑腿费加5元");
return result;
}
}
我们可以看到CGLIB的实现方法:
- 继承目标类:生成的代理类其实是目标类的子类(比如真实奶茶店$$EnhancerByCGLIB)
- 重写方法:代理类会重写父类方法,加入增强逻辑(如日志、收费)
一个表格在总结下JDK代理,和CGLIB代理:

Spring中的代理
1. Spring如何选择代理方式?
objectivec
if (目标类实现了接口 && !强制使用CGLIB) {
使用JDK动态代理;
} else {
使用CGLIB;
}
- 还是用一个图总结一下JDK动态代理和CGLIB动态代理:

到这,关于代理相关知识已经全部写完了,可以看到,文章中并没有用大量的底层具体实现代码去阐述,可以看到的是,为了减少阅读负担,已经对现有实例代码做出了优化展示。 另外还有一些碎碎念,虽然在本文中,是基于在实际应用中的一些总结,但是,我还是希望大家能有一些发散性的思维,不要形成思维定式,比如:虽然我们说CGLIB用于动态代理没有接口的类,难道就不能代理有接口的类?答案是当然可以。 后面我们就可以带着我们的理解,去阅读源码,或者相关的业务代码,肯定都是事半功倍的。