代理
什么是代理
代理模式(Proxy Pattern) 的核心思想是:为其他对象提供一种代理以控制对这个对象的访问。
简单来说,你不再直接调用"目标对象",而是调用"代理对象"。代理对象就像一个"中间商",它可以在不修改目标对象代码的前提下,为目标对象增强功能(如加日志、权限控制、事务管理等)。
代理有几种有什么区别(实现 性能 等的区别)
在 Java 中,代理模式主要分为 静态代理 和 动态代理 ,而动态代理又细分为 JDK 动态代理 和 CGLIB 动态代理。
| 特性 | 静态代理 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|---|
| 实现原理 | 手写代理类,硬编码。 | 基于 Java 反射 机制。 | 基于 ASM 字节码 框架。 |
| 对被代理类的要求 | 必须实现接口。 | 必须实现接口。 | 不需要接口,但不能是 final 类。 |
| 生成代理对象时机 | 编译期生成 .class 文件。 |
运行时动态生成。 | 运行时动态生成。 |
| 性能 (生成对象) | 最高(无需动态生成)。 | 较高。 | 较低(需要计算字节码)。 |
| 性能 (执行方法) | 最高。 | 较低(反射调用)。 | 较高(直接方法调用)。 |
① 静态代理
-
实现:代理类和目标类实现相同的接口。在代理类中手动持有一个目标对象的引用。
-
缺点 :类爆炸。每一个目标类都需要对应一个代理类,如果接口增加方法,所有代理类都要改,维护成本极高。
② JDK 动态代理(标准实现)
-
实现 :利用
java.lang.reflect.Proxy类和InvocationHandler接口。它在内存中创建一个实现了目标接口的匿名类。 -
优势:Java 原生支持,不需要引入第三方库,内存占用小。
-
局限 :只能代理接口。如果一个类没有实现接口,JDK 代理就无能为力。
③ CGLIB 动态代理(第三方实现)
-
实现:通过继承目标类并重写其方法来实现代理(生成子类)。
-
优势:可以代理没有实现接口的普通类。
-
性能:在方法调用上,CGLIB 通常快于 JDK 代理,因为它避免了反射。
-
局限 :无法代理
final类或final方法(因为无法被继承/重写)。
.性能深度对比
-
创建开销:
-
JDK 动态代理创建对象的速度非常快,因为它只涉及简单的反射生成。
-
CGLIB 创建对象较慢,因为它需要通过 ASM 框架操作复杂的字节码。
-
-
执行开销:
-
JDK 早期版本执行较慢,但从 JDK 8 开始,其反射性能大幅提升。
-
CGLIB 因为生成的代理类直接调用父类方法,没有反射过程,通常在大量执行时性能略优。
-
使用场景:我该选哪种?
-
Spring 框架的策略:
-
如果目标对象实现了接口 ,默认使用 JDK 动态代理。
-
如果目标对象没有实现接口 ,则使用 CGLIB。
-
(在 Spring Boot 2.x 后,默认配置倾向于统一使用 CGLIB 以减少因转换产生的错误)。
-
-
个人建议:
-
如果是轻量级、简单的接口代理,首选 JDK(原生、省内存)。
-
如果是高性能架构,或者需要代理具体类,选择 CGLIB(或其升级版 ByteBuddy)。
-
代理的使用场景
在 Java 开发中,代理模式(Proxy Pattern)的应用几乎无处不在。它的核心价值在于:在不修改原始代码的前提下,为方法调用添加额外的功能。
以下是代理模式的五大经典使用场景:
- 面向切面编程 (AOP) --- 最核心场景
Spring AOP 的底层完全基于动态代理。它将那些与业务无关、却被业务模块共同调用的逻辑(如日志、事务、安全检查)封装起来。
-
日志记录:自动在每个业务方法执行前记录请求参数,执行后记录耗时。
-
权限校验:在进入核心方法前,统一拦截并判断当前用户是否有权操作。
-
性能监控:统计方法的平均执行时间,寻找系统瓶颈。
- 声明式事务管理
这是 Java 后台开发中最常用的功能。当你给方法加上 @Transactional 注解时,Spring 实际上创建了一个代理对象。
-
场景:
-
代理对象先开启事务(
connection.setAutoCommit(false))。 -
调用你的业务代码。
-
如果没抛异常,代理对象提交事务。
-
如果抛了异常,代理对象负责回滚(
rollback)。
-
- 远程代理 (RPC 框架)
在分布式架构(如 Dubbo、gRPC 或 Spring Cloud)中,你可以在本地代码中直接调用远程服务器上的方法,感觉就像在本地调用一样。
-
原理:你调用的其实是一个"假"的本地代理对象。
-
过程:代理对象负责将你的请求参数序列化,通过网络发送给远程服务器,接收结果并返回给你。
- 延迟加载 (Lazy Loading)
为了节省内存和提升启动速度,有时候我们不希望立即加载一个庞大且沉重的对象(如数据库大对象或复杂的图片资源)。
-
做法:先返回一个轻量级的代理对象。
-
触发:直到你真正调用该对象的某个方法时,代理对象才会去加载真正的原始对象。
-
经典应用:Hibernate 或 MyBatis 中的关联查询(只有访问到该属性时才去查数据库)。
- 缓存代理
为了减轻数据库或复杂计算的压力,可以用代理类来控制对原始对象的访问。
-
场景:当调用某个查询方法时,代理类先去 Redis 或内存中找:
-
命中:直接返回缓存数据。
-
未命中:才去调用真实的业务对象查库,并将结果存入缓存。
-
- 接口适配与防火墙
-
数据校验:在调用接口前,通过代理层统一校验数据的格式是否合法。
-
异常统一处理:通过代理拦截所有业务异常,并将其转换为标准的 API 响应格式。
总结:为什么要用代理?
| 理由 | 说明 |
|---|---|
| 开闭原则 | 无需修改原有的 Service 类,就能增加新功能。 |
| 关注点分离 | 业务代码只需关注业务,把杂活(日志、事务)交给代理。 |
| 保护目标对象 | 代理可以作为防火墙,过滤掉非法的非法请求。 |
代理的三个代码(放到博客和笔记)
代理的优缺点
在 Java 开发中,无论是使用静态代理还是动态代理,其核心目的都是在不修改源码的情况下增强功能。但在引入代理模式时,开发者必须权衡它带来的便利与代价。
以下是代理模式(尤其是动态代理)的优缺点深度解析:
- 代理模式的优点
① 完美的"开闭原则"(OCP)实现
代理模式允许你在不改动原有业务代码的前提下,增加新功能(如日志、事务、限流)。这保证了核心业务逻辑的纯粹性,符合"对扩展开放,对修改关闭"的设计思想。
② 关注点分离(解耦)
通过代理,你可以将那些与业务逻辑无关的"横切关注点"(Cross-cutting Concerns)剥离出来。
-
业务开发人员:只关心业务流程。
-
架构师/代理层:统一处理权限、性能统计、异常捕获等通用逻辑。
③ 控制访问与保护目标对象
代理类可以作为目标对象前的"防火墙"。
-
权限控制:在调用真实方法前,检查调用者是否有权操作。
-
参数预处理:在请求到达业务层之前,先进行数据清洗或格式校验。
④ 节省资源(延迟加载)
通过虚拟代理,可以在真正需要使用对象时才去创建昂贵的资源(如大对象、数据库连接),从而优化系统的启动速度和内存使用。
- 代理模式的缺点
① 性能开销
这是代理模式最明显的副作用:
-
动态生成开销:动态代理(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(); // 通过代理对象调用,增强逻辑生效
}
}
- 针对"类爆炸与复杂度"的优化
-
链式拦截器(Interceptor Chain) : 不要为每个功能创建一个代理类。参考 Spring 的做法,将日志、权限、事务等功能做成独立的拦截器(Interceptor),然后将它们组合成一条链。这样只需要一个代理对象,就能完成多重增强。
-
限定代理范围: 不要对整个包进行盲目的"切面扫描"。通过精准的注解(Annotation)或更严格的切点表达式(Pointcut),只代理那些确实需要增强的方法,减少扫描时间和无效拦截。
- 针对"设计局限性"的优化(接口 vs 类)
-
面向接口编程 : 尽可能保持面向接口的设计,这样可以使用标准的 JDK 动态代理。它比 CGLIB 更轻量,且不涉及复杂的子类生成,内存占用更小。
-
利用桥接模式 : 如果目标类是
final(无法被 CGLIB 代理),可以考虑使用静态组合(装饰者模式)。虽然需要手写少量代码,但它回避了字节码生成的复杂性,且逻辑最清晰。
| 优化目标 | 推荐手段 |
|---|---|
| 追求极致运行速度 | 放弃动态代理,改用 AspectJ 静态织入。 |
| 提升动态生成效率 | 弃用 CGLIB,拥抱 ByteBuddy。 |
| 修复事务/代理失效 | 使用 自注入 或显式获取代理引用。 |
| 降低维护成本 | 采用 注解驱动 + 拦截器链。 |
静态代码 cglib动态代理 jdk动态代理代码的实现
- 静态代理 (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("静态代理:发送后清理数据");
}
}
- 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");
- 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");