java中的代理详解

代理

什么是代理

代理模式(Proxy Pattern) 的核心思想是:为其他对象提供一种代理以控制对这个对象的访问。

简单来说,你不再直接调用"目标对象",而是调用"代理对象"。代理对象就像一个"中间商",它可以在不修改目标对象代码的前提下,为目标对象增强功能(如加日志、权限控制、事务管理等)。

代理有几种有什么区别(实现 性能 等的区别)

在 Java 中,代理模式主要分为 静态代理动态代理 ,而动态代理又细分为 JDK 动态代理CGLIB 动态代理

特性 静态代理 JDK 动态代理 CGLIB 动态代理
实现原理 手写代理类,硬编码。 基于 Java 反射 机制。 基于 ASM 字节码 框架。
对被代理类的要求 必须实现接口。 必须实现接口 不需要接口,但不能是 final 类
生成代理对象时机 编译期生成 .class 文件。 运行时动态生成。 运行时动态生成。
性能 (生成对象) 最高(无需动态生成)。 较高。 较低(需要计算字节码)。
性能 (执行方法) 最高。 较低(反射调用)。 较高(直接方法调用)。

① 静态代理

  • 实现:代理类和目标类实现相同的接口。在代理类中手动持有一个目标对象的引用。

  • 缺点类爆炸。每一个目标类都需要对应一个代理类,如果接口增加方法,所有代理类都要改,维护成本极高。

② JDK 动态代理(标准实现)

  • 实现 :利用 java.lang.reflect.Proxy 类和 InvocationHandler 接口。它在内存中创建一个实现了目标接口的匿名类。

  • 优势:Java 原生支持,不需要引入第三方库,内存占用小。

  • 局限只能代理接口。如果一个类没有实现接口,JDK 代理就无能为力。

③ CGLIB 动态代理(第三方实现)

  • 实现:通过继承目标类并重写其方法来实现代理(生成子类)。

  • 优势:可以代理没有实现接口的普通类。

  • 性能:在方法调用上,CGLIB 通常快于 JDK 代理,因为它避免了反射。

  • 局限 :无法代理 final 类或 final 方法(因为无法被继承/重写)。

.性能深度对比

  1. 创建开销

    • JDK 动态代理创建对象的速度非常快,因为它只涉及简单的反射生成。

    • CGLIB 创建对象较慢,因为它需要通过 ASM 框架操作复杂的字节码。

  2. 执行开销

    • JDK 早期版本执行较慢,但从 JDK 8 开始,其反射性能大幅提升。

    • CGLIB 因为生成的代理类直接调用父类方法,没有反射过程,通常在大量执行时性能略优。


使用场景:我该选哪种?

  • Spring 框架的策略

    • 如果目标对象实现了接口 ,默认使用 JDK 动态代理

    • 如果目标对象没有实现接口 ,则使用 CGLIB

    • (在 Spring Boot 2.x 后,默认配置倾向于统一使用 CGLIB 以减少因转换产生的错误)。

  • 个人建议

    • 如果是轻量级、简单的接口代理,首选 JDK(原生、省内存)。

    • 如果是高性能架构,或者需要代理具体类,选择 CGLIB(或其升级版 ByteBuddy)。

代理的使用场景

在 Java 开发中,代理模式(Proxy Pattern)的应用几乎无处不在。它的核心价值在于:在不修改原始代码的前提下,为方法调用添加额外的功能。

以下是代理模式的五大经典使用场景:

  1. 面向切面编程 (AOP) --- 最核心场景

Spring AOP 的底层完全基于动态代理。它将那些与业务无关、却被业务模块共同调用的逻辑(如日志、事务、安全检查)封装起来。

  • 日志记录:自动在每个业务方法执行前记录请求参数,执行后记录耗时。

  • 权限校验:在进入核心方法前,统一拦截并判断当前用户是否有权操作。

  • 性能监控:统计方法的平均执行时间,寻找系统瓶颈。


  1. 声明式事务管理

这是 Java 后台开发中最常用的功能。当你给方法加上 @Transactional 注解时,Spring 实际上创建了一个代理对象。

  • 场景

    1. 代理对象先开启事务(connection.setAutoCommit(false))。

    2. 调用你的业务代码。

    3. 如果没抛异常,代理对象提交事务。

    4. 如果抛了异常,代理对象负责回滚(rollback)。


  1. 远程代理 (RPC 框架)

在分布式架构(如 Dubbo、gRPC 或 Spring Cloud)中,你可以在本地代码中直接调用远程服务器上的方法,感觉就像在本地调用一样。

  • 原理:你调用的其实是一个"假"的本地代理对象。

  • 过程:代理对象负责将你的请求参数序列化,通过网络发送给远程服务器,接收结果并返回给你。


  1. 延迟加载 (Lazy Loading)

为了节省内存和提升启动速度,有时候我们不希望立即加载一个庞大且沉重的对象(如数据库大对象或复杂的图片资源)。

  • 做法:先返回一个轻量级的代理对象。

  • 触发:直到你真正调用该对象的某个方法时,代理对象才会去加载真正的原始对象。

  • 经典应用:Hibernate 或 MyBatis 中的关联查询(只有访问到该属性时才去查数据库)。


  1. 缓存代理

为了减轻数据库或复杂计算的压力,可以用代理类来控制对原始对象的访问。

  • 场景:当调用某个查询方法时,代理类先去 Redis 或内存中找:

    • 命中:直接返回缓存数据。

    • 未命中:才去调用真实的业务对象查库,并将结果存入缓存。


  1. 接口适配与防火墙
  • 数据校验:在调用接口前,通过代理层统一校验数据的格式是否合法。

  • 异常统一处理:通过代理拦截所有业务异常,并将其转换为标准的 API 响应格式。

总结:为什么要用代理?

理由 说明
开闭原则 无需修改原有的 Service 类,就能增加新功能。
关注点分离 业务代码只需关注业务,把杂活(日志、事务)交给代理。
保护目标对象 代理可以作为防火墙,过滤掉非法的非法请求。

代理的三个代码(放到博客和笔记)

代理的优缺点

在 Java 开发中,无论是使用静态代理还是动态代理,其核心目的都是在不修改源码的情况下增强功能。但在引入代理模式时,开发者必须权衡它带来的便利与代价。

以下是代理模式(尤其是动态代理)的优缺点深度解析:


  1. 代理模式的优点

① 完美的"开闭原则"(OCP)实现

代理模式允许你在不改动原有业务代码的前提下,增加新功能(如日志、事务、限流)。这保证了核心业务逻辑的纯粹性,符合"对扩展开放,对修改关闭"的设计思想。

② 关注点分离(解耦)

通过代理,你可以将那些与业务逻辑无关的"横切关注点"(Cross-cutting Concerns)剥离出来。

  • 业务开发人员:只关心业务流程。

  • 架构师/代理层:统一处理权限、性能统计、异常捕获等通用逻辑。

③ 控制访问与保护目标对象

代理类可以作为目标对象前的"防火墙"。

  • 权限控制:在调用真实方法前,检查调用者是否有权操作。

  • 参数预处理:在请求到达业务层之前,先进行数据清洗或格式校验。

④ 节省资源(延迟加载)

通过虚拟代理,可以在真正需要使用对象时才去创建昂贵的资源(如大对象、数据库连接),从而优化系统的启动速度和内存使用。


  1. 代理模式的缺点

① 性能开销

这是代理模式最明显的副作用:

  • 动态生成开销:动态代理(JDK/CGLIB)在运行时需要生成字节码,这比直接调用会有微小的延迟。

  • 反射与调用链:每次方法调用都要经过代理类的拦截和反射跳转(JDK)或拦截器链(CGLIB),在大规模高并发场景下,累积的耗时可能成为性能瓶颈。

② 增加系统复杂度

  • 类结构复杂化:引入代理后,代码的调用链路变长。初学者在调试(Debug)时,会发现堆栈信息变得非常混乱,很难一眼看出真实的调用顺序。

  • 维护成本:如果是静态代理,当接口增加方法时,代理类也必须同步修改,导致维护难度倍增。

③ 设计局限性

  • JDK 代理:要求目标类必须实现接口。如果一个类没有接口,就必须引入第三方库(如 CGLIB)。

  • CGLIB 代理 :基于继承实现。如果目标类是 final 的,或者方法是 final 的,则无法被继承或重写,从而导致代理失效。

④ "自我调用"失效问题

这是 Java 代理中最著名的"坑"。

  • 现象 :如果类 A 的方法 method1() 内部直接调用了同一个类中的 method2()

  • 后果 :此时 method2() 的代理逻辑(如事务)会失效 。因为内部调用是直接通过 this 触发的,没有经过代理对象。

维度 评价 影响程度
可维护性 极高(无需改动原有代码) ⭐⭐⭐⭐⭐
开发效率 高(利用 AOP 大幅减少重复代码) ⭐⭐⭐⭐
运行效率 略微下降(反射或字节码注入开销) ⭐⭐
调试难度 增加(调用链变长,存在隐式跳转) ⭐⭐⭐

总结与应用建议

  • 绝大多数情况下: 代理的优点远大于缺点 。例如 Spring 的事务管理,虽然有微小的性能损耗,但它避免了在每个 Service 里手动写 try-catch-rollback 的巨大痛苦。

  • 极端性能场景: 如果你的系统对微秒级延迟极度敏感(如高频量化交易),应尽量减少动态代理的使用,改用编译期织入(AspectJ)或者硬编码。

对代理却带你的优化

方案 A:通过 AopContext 获取当前代理对象 : 在 Spring 中开启 exposeProxy=true,然后通过 AopContext.currentProxy() 强制触发代理调用。

java 复制代码
((Service) AopContext.currentProxy()).method2();

方案 B:自注入(Self-Injection): 在类内部注入一个自身的实例(Spring 4.3+ 支持)。

java 复制代码
@Service
public class MyService {
    @Autowired
    private MyService self; // 注入的是代理对象
​
    public void method1() {
        self.method2(); // 通过代理对象调用,增强逻辑生效
    }
}
  1. 针对"类爆炸与复杂度"的优化
  • 链式拦截器(Interceptor Chain) : 不要为每个功能创建一个代理类。参考 Spring 的做法,将日志、权限、事务等功能做成独立的拦截器(Interceptor),然后将它们组合成一条链。这样只需要一个代理对象,就能完成多重增强。

  • 限定代理范围: 不要对整个包进行盲目的"切面扫描"。通过精准的注解(Annotation)或更严格的切点表达式(Pointcut),只代理那些确实需要增强的方法,减少扫描时间和无效拦截。


  1. 针对"设计局限性"的优化(接口 vs 类)
  • 面向接口编程 : 尽可能保持面向接口的设计,这样可以使用标准的 JDK 动态代理。它比 CGLIB 更轻量,且不涉及复杂的子类生成,内存占用更小。

  • 利用桥接模式 : 如果目标类是 final(无法被 CGLIB 代理),可以考虑使用静态组合(装饰者模式)。虽然需要手写少量代码,但它回避了字节码生成的复杂性,且逻辑最清晰。

优化目标 推荐手段
追求极致运行速度 放弃动态代理,改用 AspectJ 静态织入。
提升动态生成效率 弃用 CGLIB,拥抱 ByteBuddy
修复事务/代理失效 使用 自注入 或显式获取代理引用。
降低维护成本 采用 注解驱动 + 拦截器链

静态代码 cglib动态代理 jdk动态代理代码的实现

  1. 静态代理 (Static Proxy)

原理: 代理类与目标类实现相同的接口,并在代理类中硬编码增强逻辑。

java 复制代码
// 1. 定义接口
interface SmsService {
    void send(String message);
}
​
// 2. 目标对象(被代理类)
class SmsServiceImpl implements SmsService {
    public void send(String message) {
        System.out.println("发送短信: " + message);
    }
}
​
// 3. 代理类
class SmsProxy implements SmsService {
    private final SmsService target;
​
    public SmsProxy(SmsService target) {
        this.target = target;
    }
​
    @Override
    public void send(String message) {
        System.out.println("静态代理:发送前记录日志"); // 增强逻辑
        target.send(message);
        System.out.println("静态代理:发送后清理数据");
    }
}
  1. JDK 动态代理 (JDK Dynamic Proxy)

原理: 利用 Java 反射机制。代理类在运行时动态生成,必须有接口

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载器
                target.getClass().getInterfaces(),  // 目标类实现的接口
                new InvocationHandler() {           // 代理的具体逻辑
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK动态代理:方法执行前 - " + method.getName());
                        Object result = method.invoke(target, args); // 反射调用目标方法
                        System.out.println("JDK动态代理:方法执行后");
                        return result;
                    }
                }
        );
    }
}
​
// 使用示例:
// SmsService proxy = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
// proxy.send("Hello JDK");
  1. CGLIB 动态代理 (CGLIB Dynamic Proxy)

原理: 通过继承目标类并重写方法来实现。不需要接口 ,但目标类不能是 final

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
​
// 注意:通常需要引入 cglib 依赖
class CglibProxyFactory implements MethodInterceptor {
    public Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);      // 设置父类(即目标类)
        enhancer.setCallback(this);         // 设置回调
        return enhancer.create();           // 创建代理对象
    }
​
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB代理:执行前准备...");
        Object result = proxy.invokeSuper(obj, args); // 注意:这里调用的是父类方法
        System.out.println("CGLIB代理:执行后结束");
        return result;
    }
}
​
// 使用示例:
// CglibProxyFactory factory = new CglibProxyFactory();
// SmsServiceImpl proxy = (SmsServiceImpl) factory.getProxy(SmsServiceImpl.class);
// proxy.send("Hello CGLIB");
相关推荐
野生技术架构师2 小时前
2025年Java面试八股文大全(附PDF版)
java·面试·pdf
Coder_Boy_2 小时前
SpringAI与LangChain4j的智能应用-(实践篇4)
java·人工智能·spring boot·langchain
CC.GG2 小时前
【Qt】常用控件----QWidget属性
java·数据库·qt
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-13-多线程安全-锁机制-底层核心实现机制
java·开发语言
萤丰信息2 小时前
数智重构生态:智慧园区引领城市高质量发展新范式
java·大数据·人工智能·安全·智慧城市
悟空码字2 小时前
MySQL分库分表,从“一室一厅”到“豪华别墅区”的数据库升级之旅
java·后端·mysql
Lisonseekpan2 小时前
RBAC 基于角色的访问控制模型详解与实践指南
java·服务器·网络·后端·spring·log4j
奔跑的小十一2 小时前
ShardingSphere-JDBC 开发手册
java·数据库