引言
在深入学习 Spring 框架的过程中,你一定会频繁接触到"代理"(Proxy)这个概念。无论是 AOP(面向切面编程) 、事务管理 、缓存 ,还是 作用域 Bean 的注入 (如 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)),其底层实现都离不开代理模式。
然而,很多初学者对代理的理解停留在"Spring 会自动创建代理对象"的模糊层面,不清楚:
- 什么是代理模式?
- Spring 为何需要代理?
- JDK 动态代理和 CGLIB 代理有何区别?
- 什么时候用哪种代理?
- 代理会带来哪些性能开销或陷阱?
本文将从设计模式基础出发,系统性地讲解 代理模式在 Spring 中的应用,涵盖以下内容:
- 代理模式的基本概念与分类
- JDK 动态代理原理与实战
- CGLIB 代理原理与实战
- Spring 如何自动选择代理策略
- 代理在 AOP、事务、Scope 中的具体应用
- 常见问题与最佳实践
- Spring Boot 中的代理行为差异
无论你是刚接触 Spring 的新手,还是希望深入理解其底层机制的开发者,本文都将为你揭开 Spring 代理的神秘面纱。
第一章:代理模式的基本概念
1.1 什么是代理模式?
代理模式(Proxy Pattern) 是 GoF 23 种设计模式之一,属于结构型模式 。其核心思想是:为其他对象提供一种代理以控制对这个对象的访问。
通俗理解:代理就像"中介"或"替身"。你不能直接接触目标对象,而是通过代理来间接操作它,并在过程中加入额外逻辑(如权限检查、日志记录、延迟加载等)。
1.2 代理模式的通用结构
// 接口
public interface Subject {
void request();
}
// 真实主题(被代理对象)
public class RealSubject implements Subject {
public void request() {
System.out.println("真实业务执行");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
// 前置增强
System.out.println("权限检查...");
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request();
// 后置增强
System.out.println("记录日志...");
}
}
调用方使用 Proxy 而非 RealSubject,从而在不修改原逻辑的前提下增强功能。
1.3 代理的分类(按创建时机)
| 类型 | 说明 | 示例 |
|---|---|---|
| 静态代理 | 编译时手动编写代理类 | 上述代码 |
| 动态代理 | 运行时自动生成代理类 | JDK Proxy、CGLIB |
Spring 主要使用动态代理,因为它更灵活、可复用。
第二章:JDK 动态代理
2.1 原理
JDK 动态代理基于 Java 反射机制 ,要求被代理类必须实现至少一个接口 。代理对象在运行时通过 Proxy.newProxyInstance() 动态生成,其实现了相同的接口,并将方法调用委托给 InvocationHandler。
2.2 核心 API
java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler
2.3 实战示例
// 1. 定义接口
public interface UserService {
void saveUser(String name);
}
// 2. 实现类
public class UserServiceImpl implements UserService {
public void saveUser(String name) {
System.out.println("保存用户: " + name);
}
}
// 3. 实现代理处理器
public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【前置】调用方法: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("【后置】方法执行完毕");
return result;
}
}
// 4. 创建代理
public class Main {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
proxy.saveUser("张三");
}
}
输出:
【前置】调用方法: saveUser
保存用户: 张三
【后置】方法执行完毕
2.4 特点
✅ 优点:
- JDK 内置,无需额外依赖。
- 生成的代理类是
$Proxy0这样的匿名类,干净简洁。
❌ 缺点:
- 只能代理实现了接口的类。
- 如果目标类没有接口,JDK 代理无法使用。
第三章:CGLIB 动态代理
3.1 原理
CGLIB(Code Generation Library)是一个字节码生成库 ,它通过继承目标类并重写其方法来实现代理。因此,不要求目标类实现接口。
CGLIB 底层使用 ASM 字节码操作框架,在运行时动态生成子类。
3.2 添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
注意:Spring Boot 项目中通常已间接引入(通过
spring-core)。
3.3 实战示例
// 1. 目标类(无接口!)
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
}
// 2. 实现代理回调
public class OrderMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB 前置】" + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("【CGLIB 后置】");
return result;
}
}
// 3. 创建代理
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new OrderMethodInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("1001");
}
}
输出:
【CGLIB 前置】createOrder
创建订单: 1001
【CGLIB 后置】
3.4 特点
✅ 优点:
- 可代理任意类 (只要不是
final)。 - 性能略优于 JDK 代理(尤其在大量方法调用时)。
❌ 缺点:
- 无法代理
final类或final方法(因为不能被继承/重写)。 - 生成的代理类是目标类的子类,可能引发类型判断问题(如
instanceof)。
第四章:Spring 如何选择代理策略?
Spring 在启用 AOP 或需要代理时,会自动选择最合适的代理方式。其决策逻辑如下:
4.1 默认策略(Spring 5+)
| 条件 | 使用的代理 |
|---|---|
| 目标类实现了接口 | 优先使用 JDK 动态代理 |
| 目标类未实现接口 | 强制使用 CGLIB 代理 |
这是 Spring 的默认行为,符合"面向接口编程"的最佳实践。
4.2 强制使用 CGLIB
可通过配置强制所有 Bean 使用 CGLIB:
XML 方式:
<aop:config proxy-target-class="true"/>
注解方式(Spring Boot):
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration
public class AopConfig {}
建议:除非有特殊需求(如代理无接口的类),否则保持默认即可。
4.3 查看实际使用的代理类型
@Autowired
private UserService userService;
@Test
public void testProxyType() {
System.out.println(userService.getClass());
// 输出:class com.sun.proxy.$Proxy42 (JDK 代理)
// 或:class com.example.UserServiceImpl$$EnhancerBySpringCGLIB$$1a2b3c (CGLIB 代理)
}
第五章:代理在 Spring 核心功能中的应用
5.1 AOP(面向切面编程)
AOP 是代理最典型的应用。例如:
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法调用前: " + joinPoint.getSignature());
}
}
Spring 会为目标 Service 类创建代理,所有方法调用都会经过切面逻辑。
✅ 注意 :只有通过代理对象调用的方法才会被拦截 !
自调用(this.method())不会触发 AOP!
@Service
public class OrderService {
public void placeOrder() {
this.validate(); // ❌ 不会触发 AOP!
validate(); // ❌ 同上
}
@Transactional
public void validate() {
// ...
}
}
解决方案:
- 注入自身(不推荐,易循环依赖);
- 使用
AopContext.currentProxy()(需开启exposeProxy = true); - 重构代码,避免自调用。
5.2 声明式事务(@Transactional)
@Transactional 本质上是一个 AOP 切面。Spring 会为标注该注解的类创建事务代理:
@Service
public class AccountService {
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
// 数据库操作
}
}
- 若
AccountService实现了接口 → JDK 代理; - 否则 → CGLIB 代理。
⚠️ 常见错误 :在非 public 方法上使用
@Transactional(JDK 代理无法拦截 protected/private 方法)。
5.3 作用域代理(Scoped Proxy)
当 singleton Bean 需要注入 request/session 作用域的 Bean 时,Spring 会创建代理:
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String userId;
// ...
}
@Service
public class UserService {
@Autowired
private RequestContext requestContext; // 实际是代理对象
}
proxyMode = TARGET_CLASS→ 使用 CGLIB 代理;proxyMode = INTERFACES→ 使用 JDK 代理(要求RequestContext实现接口)。
每次调用 requestContext.getUserId() 时,代理会从当前请求上下文中获取真实的实例。
第六章:常见问题与最佳实践
6.1 问题一:代理导致的类型转换异常
UserService userService = context.getBean(UserService.class);
UserServiceImpl impl = (UserServiceImpl) userService; // ❌ ClassCastException!
原因:userService 是 $Proxy42,不是 UserServiceImpl。
解决方案:
- 编程时始终面向接口;
- 避免强转具体实现类。
6.2 问题二:final 类/方法无法被代理
@Service
public final class FinalService { // ❌ 无法被 CGLIB 代理
public void doSomething() {}
}
或:
@Service
public class MyService {
public final void criticalMethod() {} // ❌ 无法被重写
}
解决方案:
- 移除
final关键字; - 或改用编译时织入(AspectJ,非运行时代理)。
6.3 问题三:自调用不生效
如前所述,内部方法调用绕过代理。
最佳实践:
-
将需代理的方法拆分到另一个 Service;
-
使用
ApplicationContext获取代理自身(谨慎使用):@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext context;public void placeOrder() { OrderService self = context.getBean(OrderService.class); self.validate(); // ✅ 通过代理调用 } @Transactional public void validate() { ... }}
6.4 性能考量
- 代理本身有轻微性能开销(反射或方法重写);
- 但在现代 JVM 下,影响微乎其微;
- 不要因性能拒绝使用代理,其带来的解耦和可维护性远大于开销。
第七章:Spring Boot 中的代理行为
Spring Boot 默认启用了 AOP 自动配置(@EnableAspectJAutoProxy),但有几点需要注意:
7.1 默认代理策略
- 与 Spring 相同:有接口用 JDK,无接口用 CGLIB。
7.2 开启 exposeProxy
若需在自调用中使用代理,需显式开启:
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {}
然后通过 AopContext.currentProxy() 获取:
public void methodA() {
((MyService) AopContext.currentProxy()).methodB();
}
⚠️ 此方式破坏了代码的纯洁性,仅作为最后手段。
7.3 测试中的代理
在 @SpringBootTest 中,注入的 Bean 已是代理对象,可直接测试 AOP 行为:
@SpringBootTest
class TransactionalTest {
@Autowired
private AccountService accountService;
@Test
void testTransactionRollback() {
assertThrows(Exception.class, () ->
accountService.transfer("A", "B", new BigDecimal("999999"))
);
// 验证数据库未变更(事务回滚)
}
}
结语
代理模式是 Spring 实现非侵入式增强的核心机制。理解 JDK 动态代理与 CGLIB 的区别,掌握 Spring 的代理选择策略,不仅能帮助你正确使用 AOP、事务等功能,还能避免常见的"坑"。
记住以下关键点:
- 优先面向接口编程,让 Spring 自然选择 JDK 代理;
- 避免字段注入和自调用,确保代理生效;
- 不要代理 final 类/方法;
- 代理是工具,不是魔法------理解其原理才能驾驭它。
希望本文能为你构建坚实的 Spring 代理知识体系,助你在企业级开发中游刃有余。
参考资料
- Spring Framework 官方文档:AOP Proxying
- 《Spring 实战(第6版)》
- CGLIB GitHub: https://github.com/cglib/cglib
- Java Reflection and Dynamic Proxies (Oracle Docs)