一篇搞定Java中的代理

代理机制

前言

了解一个知识点的真正底层。不仅仅去聚焦于技术实现,要去仔细思考,为什么会有这个东西出现?他的作用是什么?我们要锻炼这种思维能力,任何一种技术的关键在于其实现的底层逻辑,而不是用什么计算机语言和工具去实现。

本文涉及知识点

  • 代理
  • 静态代理
  • 动态代理
  • JDK动态代理
  • CGLIB动态代理

初识代理

当面试官问出:"你知道Java中代理吗?"。身为面试者,猛然心中一惊:完了完了,平时就写写业务代码,也没用到代理。仔细想了想,不对,设计模式中好像有一个代理模式,嗯,对,java好像还有动态代理机制,然后就开始巴拉巴拉一顿输出。30s后,你看着面试官,希望从面试官脸上看出肯定的表情,然而此时的面试官心里:"小伙汁儿,八股文背的不错"。 不幸的是,此次面试也宣布凉凉。 回去的路上,只恨只平时怎么没有多积累相关的知识点。

1. 什么是代理?

大白话解释:代理就是找个靠谱的中间人帮你干活

想象你叫外卖的场景:

  1. 你自己点餐 → 你直接打电话给餐厅,告诉老板你要什么(这是直接操作)。
  2. 你用外卖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家店铺(奶茶店、炸鸡店、蛋糕店),你想让一个"私人秘书(中间人)"帮你跑腿买这些东西
  1. 定义接口(店铺协议)
csharp 复制代码
// 1. 奶茶店接口
interface 奶茶店 {
    void 制作奶茶();
}

// 2. 炸鸡店接口
interface 炸鸡店 {
    void 炸鸡();
}

// 3. 蛋糕店接口
interface 蛋糕店 {
    void 做蛋糕();
}
  1. 真实店铺(实现类)
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("【蛋糕店】蛋糕做好了!");
    }
}
  1. 静态代理:私人秘书(手动编写代理类)​
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元");
    }
}
  1. 使用静态代理​
typescript 复制代码
public class 静态代理Demo {
    public static void main(String[] args) {
        // 1. 创建真实店铺
        奶茶店 奶茶店铺 = new 真实奶茶店();
        炸鸡店 炸鸡店铺 = new 真实炸鸡店();
        蛋糕店 蛋糕店铺 = new 真实蛋糕店();

        // 2. 创建代理(私人秘书)
        奶茶店 奶茶秘书 = new 奶茶店秘书(奶茶店铺);
        炸鸡店 炸鸡秘书 = new 炸鸡店秘书(炸鸡店铺);
        蛋糕店 蛋糕秘书 = new 蛋糕店秘书(蛋糕店铺);

        // 3. 让秘书跑腿
        奶茶秘书.制作奶茶();
        炸鸡秘书.炸鸡();
        蛋糕秘书.做蛋糕();
    }
}

可以看下上面举例的静态代理的例子,有以下的缺点:

  • 每代理一个店铺,就要写一个代理类
  • 每新增一个店铺(比如咖啡店),就要写一个咖啡店秘书类
  • 如果奶茶店新增一个方法(比如制作珍珠奶茶),奶茶店秘书也要跟着改

如果我们使用的是动态代理:

  1. 定义接口(店铺协议)
csharp 复制代码
// 1. 奶茶店接口
interface 奶茶店 {
    void 制作奶茶();
}

// 2. 炸鸡店接口
interface 炸鸡店 {
    void 炸鸡();
}

// 3. 蛋糕店接口
interface 蛋糕店 {
    void 做蛋糕();
}
  1. 真实店铺(实现类)​
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("【蛋糕店】蛋糕做好了!");
    }
}
  1. 动态代理:美团外卖(自动适配所有店铺)​
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的单继承限制

  1. 所有JDK动态代理类都继承Proxy 查看反编译的代理类:
scala 复制代码
public final class $Proxy0 extends Proxy implements 你的接口 {
    // 自动生成的方法全部走InvocationHandler
}
  • 由于Java禁止多继承,代理类已经继承了Proxy,无法再继承其他类
  • 只能通过实现接口的方式扩展功能

生活比喻

  • 想象Proxy是一个万能插座基座:
  • 它已经占用了你家的唯一插座位(继承位)
  • 你只能通过外接插线板(实现接口)扩展功能

CGLIB 动态代理

在日常的开发中,除了JDK自带的动态代理类,还有就是CGLIB的动态代理类,这个是Spring整合的第三方库,还是用奶茶店的例子来阐述。

  1. 真实店铺(没有接口!)
csharp 复制代码
// 1. 真实奶茶店(没有实现任何接口!)
class 真实奶茶店 {
    public void 制作奶茶() {
        System.out.println("【奶茶店】奶茶做好了!");
    }
}

// 2. 真实炸鸡店(也没有接口!)
class 真实炸鸡店 {
    public void 炸鸡() {
        System.out.println("【炸鸡店】炸鸡做好了!");
    }
}
  1. 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;
}
  1. 还是用一个图总结一下JDK动态代理和CGLIB动态代理:

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

相关推荐
我最厉害。,。1 小时前
接口安全&SOAP&OpenAPI&RESTful&分类特征导入&项目联动检测
后端·restful
AntBlack3 小时前
计算机视觉 : 端午无事 ,图像处理入门案例一文速通
后端·python·计算机视觉
福大大架构师每日一题5 小时前
2025-06-02:最小可整除数位乘积Ⅱ。用go语言,给定一个表示正整数的字符串 num 和一个整数 t。 定义:如果一个整数的每一位都不是 0,则称该整数为
后端
Code_Artist5 小时前
[Mybatis] 因 0 != null and 0 != '' 酿成的事故,害得我又过点啦!
java·后端·mybatis
程序员博博5 小时前
看到这种代码,我直接气到想打人
后端
南雨北斗5 小时前
php 图片压缩函数
后端
L2ncE5 小时前
ES101系列08 | 数据建模和索引重建
java·后端·elasticsearch
还是鼠鼠5 小时前
Maven---配置本地仓库
java·开发语言·后端·maven
无问8175 小时前
SpringBoot:统一功能处理、拦截器、适配器模式
spring boot·后端·适配器模式
一只叫煤球的猫6 小时前
MySQL虚拟列:一个被低估的MySQL特性
数据库·后端·mysql