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");
相关推荐
num_killer4 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode5 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐5 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲5 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红5 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥5 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v5 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地5 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209256 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei6 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot