什么是代理模式?详解Java中Proxy的底层实现与应用场景

来源:语言推理作者:沙月恵奈‌头衔:网络博主
导读:本期聚焦于沙月恵奈‌创作的《什么是代理模式?详解Java中Proxy的底层实现与应用场景》,敬请观看详情。代理模式是设计模式中结构简单却应用极广的一种,它通过引入代理对象来控制对目标对象的访问。本文从静态代理入手,分析代理模式的构成三要素,进而深入JDK动态代理的实现机制,讲解InvocationHandler接口、Proxy.newProxyInstance方法的执行流程,并对比CGLIB与JDK代理在实现方式和性能上的差异。文中还结合RPC远程调用、AOP切面编程、延迟加载等真实场景,说明代理模式在主流框架中的落地方式,帮助你在理解原理的基础上正确选用代理方案。

代理模式的核心思想说起来很简单:不直接访问目标对象,而是通过一个中间对象来间接访问,由这个中间对象负责控制访问的时机、方式和附加逻辑。这个中间对象就是代理,它和目标对象实现相同的接口,调用方感知不到差异。Spring AOP、MyBatis的Mapper接口、RPC框架的服务引用,背后几乎都是代理模式在支撑。要真正理解这些框架,绕不开代理模式本身以及JDK中Proxy的底层实现。

什么是代理模式?详解Java中Proxy的底层实现与应用场景

代理模式的基本结构与静态代理

代理模式的标准定义是:为其他对象提供一种代理以控制对这个对象的访问。它包含三个角色。第一个是抽象主题接口,定义了目标对象和代理对象共同的行为契约;第二个是真实主题,即实际干活的目标对象;第三个是代理对象,它持有目标对象的引用,在调用前后可以插入额外逻辑。

静态代理是最直观的实现方式:程序员手动编写一个代理类,在编译期就确定代理关系。下面用一个简单的订单保存例子来演示:

// 抽象主题接口
public interface OrderService {
    void saveOrder(String orderNo);
}

// 真实主题
public class OrderServiceImpl implements OrderService {
    @Override
    public void saveOrder(String orderNo) {
        System.out.println("保存订单:" + orderNo);
    }
}

// 静态代理类
public class OrderServiceProxy implements OrderService {
    private final OrderService target;

    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public void saveOrder(String orderNo) {
        System.out.println("记录方法调用日志...");
        target.saveOrder(orderNo);
        System.out.println("记录方法执行耗时...");
    }
}

静态代理的优点是结构清晰、易于调试,所有逻辑都是显式代码。但缺点也很致命:每个接口每个方法都要手写一遍转发逻辑,方法一多代码大量重复;而且一旦接口增加方法,代理类必须同步修改。如果项目里有几十个接口都需要统一的日志或事务控制,手写代理就完全不可接受了,这就引出了动态代理。

JDK动态代理的底层实现机制

JDK从1.3开始提供了动态代理支持,核心入口是java.lang.reflect.Proxy类和InvocationHandler接口。与静态代理不同,动态代理的代理类是在运行时动态生成的,开发者只需要编写横切逻辑,不需要为每个接口单独写代理类。

使用方式分两步:先实现InvocationHandler,在它的invoke方法中编写统一处理逻辑;再调用Proxy.newProxyInstance生成代理实例。看一个完整示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyDemo {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();

        OrderService proxy = (OrderService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
                        System.out.println("前置处理:方法名 " + method.getName());
                        Object result = method.invoke(target, arguments);
                        System.out.println("后置处理");
                        return result;
                    }
                });

        proxy.saveOrder("SO-1001");
    }
}

深入到实现层面,Proxy.newProxyInstance做了三件事。第一,它通过ProxyClassFactory在内存中生成代理类的字节码,代理类的命名规则是com.sun.proxy.$ProxyN这样的形式,并且默认继承自Proxy类,同时实现传入的全部接口。这也解释了为什么JDK动态代理要求目标对象必须实现接口——Java是单继承的,代理类已经继承了Proxy,无法再继承业务类。第二,通过传入的类加载器加载这份生成的字节码,得到Class对象。第三,通过反射调用代理类的构造器,把InvocationHandler实例作为参数传入,构造出代理对象。

生成的代理类反编译后大致是下面这个样子,可以清楚地看到每个方法体只是把调用转发给了handler:

public final class $Proxy0 extends Proxy implements OrderService {
    private static Method m1;
    private static Method m3; // saveOrder 对应的Method对象

    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    @Override
    public final void saveOrder(String orderNo) {
        try {
            // 所有方法调用统一转发给InvocationHandler
            super.h.invoke(this, m3, new Object[]{orderNo});
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

理解这段生成代码非常重要,它能解释两个常见疑惑。一是为什么在invoke方法里调用proxy参数的方法会造成死循环——因为proxy就是代理对象本身,对它调用任何接口方法都会再次进入invoke。二是为什么hashCodeequalstoString这三个Object方法也会被拦截,而getClass等final方法不会被拦截,因为生成的代理类只重写了接口方法和这三个方法。

CGLIB代理与方案选型

JDK动态代理的接口限制在很多时候并不方便,比如要代理一个没有实现任何接口的普通类,就需要CGLIB。CGLIB的原理不同:它通过ASM字节码框架生成目标类的子类,代理对象就是子类实例,通过重写非final的方法来插入拦截逻辑。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                System.out.println("CGLIB前置处理");
                Object result = methodProxy.invokeSuper(obj, args);
                System.out.println("CGLIB后置处理");
                return result;
            }
        });

        OrderServiceImpl proxy = (OrderServiceImpl) enhancer.create();
        proxy.saveOrder("SO-2002");
    }
}

两者的对比可以从几个维度展开。实现方式上,JDK代理基于接口,CGLIB基于继承;依赖上,JDK代理是JDK自带的,CGLIB需要额外引入第三方库。能力边界上,CGLIB无法代理final类和final方法,而JDK代理无法代理没有接口的类。性能方面,早期CGLIB创建代理较慢但方法调用快,JDK代理则相反,不过经过多个版本的优化,两者在调用层面的差距已经很小,性能一般不再是选型的决定因素。

Spring框架的选型策略值得参考:当目标类实现了接口时默认使用JDK动态代理,没有接口时自动切换到CGLIB,也可以通过配置强制使用CGLIB。从Spring Boot 2.x开始,默认策略已经改为优先使用CGLIB,这样能避免一个经典陷阱——JDK代理情况下,把目标对象强转成具体实现类会抛出ClassCastException,因为代理对象和实现类只是兄弟关系而非父子关系。

代理模式的典型应用场景

第一种是远程代理,这是RPC框架的基础。客户端拿到的一个服务接口引用,实际上是代理对象,调用方法时代理负责序列化参数、发起网络请求、把结果反序列化后返回,调用方完全感觉不到网络细节。Dubbo的服务引用、Feign的HTTP客户端都是这个思路。

第二种是AOP切面编程。事务管理、日志记录、权限校验、接口限流这些横切逻辑,如果写进业务代码会造成大量耦合。通过代理模式,这些逻辑被抽取到切面中,在方法调用的前后动态织入,业务代码保持纯粹。Spring的声明式事务@Transactional本质上就是通过代理在方法开启前获取连接、关闭自动提交,方法正常结束后提交,抛异常时回滚。

第三种是延迟加载,也叫虚代理。Hibernate和MyBatis的关联查询默认是懒加载的:实体返回时代理对象只填充主键,真正访问关联属性时代理才去数据库补查数据。这种方式避免了加载实体时连带着把整条关联链都查出来,对大对象的按需加载非常有效。

最后需要提醒一个常见坑:代理对象内部的自我调用不会走代理。比如同一个Service里方法A调用this.方法B(),即使方法B标注了@Transactional,事务也不会生效,因为此时调用发生在目标对象内部,绕过了代理。解决办法是通过AopContext.currentProxy()获取当前代理再调用,或者把方法拆分到不同的Bean中。理解这一点,恰恰说明了吃透Proxy实现原理对实际开发的直接价值。

代理模式动态代理Java Proxy修改时间:2026-09-16 06:42:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。