Appearance
代理模式 (Proxy Pattern)
一、定义
一句话概括:为其他对象提供一种代理,以控制对这个对象的访问。
官方定义(GoF):Provide a surrogate or placeholder for another object to control access to it.
代理模式的核心思想是:引入一个代理对象来间接访问目标对象,在访问目标对象的前后可以添加额外的控制逻辑。代理对象和目标对象实现相同的接口,客户端无法区分代理和真实对象。
二、解决的问题
2.1 什么场景下需要代理模式?
- 访问控制:控制对敏感对象的访问权限。
- 延迟加载:大对象的创建开销很大,需要延迟到真正使用时才创建。
- 远程代理:本地代理远程对象,屏蔽网络通信细节(如 RMI、gRPC)。
- 日志/监控:在不修改原代码的情况下,记录方法调用日志。
- 缓存:为耗时操作添加缓存层。
- 事务管理:在方法调用前后添加事务控制。
2.2 不用代理模式会有什么问题?
java
// 假设我们要给 UserService 的所有方法添加日志和性能监控
class UserService {
public User getUser(Long id) {
// 业务逻辑
return userRepository.findById(id);
}
public void saveUser(User user) {
// 业务逻辑
userRepository.save(user);
}
}
// 不用代理:每个方法都要手动添加日志
class UserServiceWithLog {
public User getUser(Long id) {
long start = System.currentTimeMillis();
System.out.println("开始调用 getUser, 参数: " + id);
try {
User user = userRepository.findById(id);
System.out.println("getUser 完成, 耗时: " + (System.currentTimeMillis() - start) + "ms");
return user;
} catch (Exception e) {
System.out.println("getUser 异常: " + e.getMessage());
throw e;
}
}
// saveUser 也要重复相同的日志代码...
}问题1:代码重复 —— 每个方法都要写相同的日志代码
问题2:违反单一职责 —— 业务类混杂了日志、监控等横切关注点
问题3:无法统一管理 —— 修改日志格式需要改所有方法
问题4:紧耦合 —— 日志代码和业务代码耦合在一起三、结构
3.1 文字描述
代理模式包含以下角色:
- Subject(抽象主题):定义 RealSubject 和 Proxy 的公共接口。
- RealSubject(真实主题):真正的业务对象,代理所代表的真实实体。
- Proxy(代理):持有 RealSubject 的引用,控制对 RealSubject 的访问。
3.2 ASCII 类图
┌─────────────────────────┐
│ <<interface>> │
│ Subject │
├─────────────────────────┤
│ + request() │
└────────────┬────────────┘
│
┌────────┴──────────────────────┐
│ │
┌───┴──────────────┐ ┌──────────┴──────────────┐
│ RealSubject │ │ Proxy │
├──────────────────┤ ├─────────────────────────┤
│ + request() │ │ - realSubject: Subject │
│ 实际业务逻辑 │ │ + request() │
└──────────────────┘ │ preProcess() │
│ realSubject.request() │
│ postProcess() │
└─────────────────────────┘3.3 三种代理模式对比
┌──────────────┬──────────────────────┬──────────────────────────────┐
│ 代理类型 │ 实现方式 │ 特点 │
├──────────────┼──────────────────────┼──────────────────────────────┤
│ 静态代理 │ 手动编写代理类 │ 编译时确定,一个代理对应一个接口│
│ JDK 动态代理 │ java.lang.reflect.Proxy│ 运行时生成,基于接口 │
│ CGLIB 代理 │ 字节码增强 │ 运行时生成,基于继承(无需接口)│
└──────────────┴──────────────────────┴──────────────────────────────┘四、代码实现
4.1 基础实现
场景:图片加载器(延迟加载 + 访问控制)
java
// ============ Subject:抽象主题 ============
interface Image {
void display();
String getFileName();
}
// ============ RealSubject:真实主题 ============
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(); // 模拟加载开销
}
private void loadFromDisk() {
System.out.println("从磁盘加载图片: " + fileName);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
@Override
public void display() {
System.out.println("显示图片: " + fileName);
}
@Override
public String getFileName() {
return fileName;
}
}
// ============ Proxy:代理主题(延迟加载)============
class ImageProxy implements Image {
private String fileName;
private RealImage realImage; // 延迟初始化
public ImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
// 延迟加载:只在真正需要时才创建 RealImage
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
@Override
public String getFileName() {
return fileName;
}
}
// ============ 客户端测试 ============
public class ProxyDemo {
public static void main(String[] args) {
// 创建代理时,RealImage 不会加载
Image image1 = new ImageProxy("photo1.jpg");
Image image2 = new ImageProxy("photo2.jpg");
System.out.println("代理创建完成,图片尚未加载");
// 只有真正显示时才加载图片
image1.display(); // 此时才加载 photo1.jpg
image1.display(); // 第二次显示,直接使用已加载的图片
image2.display(); // 加载 photo2.jpg
}
}4.2 进阶实现
4.2.1 JDK 动态代理
JDK 动态代理基于接口,运行时通过 java.lang.reflect.Proxy 动态生成代理类。
java
// ============ Subject 接口 ============
interface UserService {
User getUser(Long id);
void saveUser(User user);
void deleteUser(Long id);
}
// ============ RealSubject ============
class UserServiceImpl implements UserService {
@Override
public User getUser(Long id) {
System.out.println("查询用户: " + id);
return new User(id, "张三");
}
@Override
public void saveUser(User user) {
System.out.println("保存用户: " + user.getName());
}
@Override
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
}
}
// ============ InvocationHandler:调用处理器 ============
class LogInvocationHandler implements InvocationHandler {
private Object target; // 被代理的真实对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
long start = System.currentTimeMillis();
System.out.println("[LOG] 开始调用 " + method.getName() +
", 参数: " + Arrays.toString(args));
try {
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置增强
long elapsed = System.currentTimeMillis() - start;
System.out.println("[LOG] " + method.getName() +
" 完成, 耗时: " + elapsed + "ms, 结果: " + result);
return result;
} catch (Exception e) {
// 异常增强
System.out.println("[LOG] " + method.getName() +
" 异常: " + e.getMessage());
throw e;
}
}
}
// ============ 代理工厂 ============
class JdkProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 接口数组
new LogInvocationHandler(target) // 调用处理器
);
}
}
// ============ 测试 ============
public class JdkDynamicProxyDemo {
public static void main(String[] args) {
// 原始对象
UserService userService = new UserServiceImpl();
// 创建代理对象
UserService proxy = JdkProxyFactory.createProxy(userService);
// 通过代理调用
proxy.getUser(1L);
proxy.saveUser(new User(2L, "李四"));
proxy.deleteUser(3L);
// 查看生成的代理类
System.out.println("代理类: " + proxy.getClass().getName());
// 输出:com.sun.proxy.$Proxy0
}
}4.2.2 CGLIB 动态代理
CGLIB 基于继承,运行时通过字节码增强生成目标类的子类作为代理,不需要接口。
java
// ============ 目标类(没有接口)============
class ProductService {
public Product getProduct(Long id) {
System.out.println("查询商品: " + id);
return new Product(id, "iPhone 15");
}
public final void finalMethod() {
System.out.println("final 方法不能被 CGLIB 代理");
}
}
// ============ MethodInterceptor:方法拦截器 ============
class PerformanceInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("[性能监控] 开始: " + method.getName());
try {
// 调用父类(目标对象)的方法
// 注意:使用 proxy.invokeSuper 而不是 method.invoke
Object result = proxy.invokeSuper(obj, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("[性能监控] " + method.getName() +
" 耗时: " + elapsed + "ms");
// 慢查询告警
if (elapsed > 100) {
System.out.println("[性能告警] " + method.getName() +
" 执行超过 100ms!");
}
return result;
} catch (Exception e) {
System.out.println("[性能监控] " + method.getName() +
" 异常: " + e.getMessage());
throw e;
}
}
}
// ============ CGLIB 代理工厂 ============
class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz); // 设置父类
enhancer.setCallback(new PerformanceInterceptor());
return (T) enhancer.create();
}
}
// ============ 测试 ============
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建 CGLIB 代理
ProductService proxy = CglibProxyFactory.createProxy(ProductService.class);
proxy.getProduct(1L);
// finalMethod 不会被代理
proxy.finalMethod();
System.out.println("代理类: " + proxy.getClass().getName());
// 输出:ProductService$$EnhancerByCGLIB$$xxxxx
}
}4.2.3 JDK 动态代理 vs CGLIB 代理对比
java
// ============ 对比总结 ============
/*
┌──────────────┬──────────────────────┬──────────────────────────────┐
│ 对比维度 │ JDK 动态代理 │ CGLIB 代理 │
├──────────────┼──────────────────────┼──────────────────────────────┤
│ 实现原理 │ 基于接口 + 反射 │ 基于继承 + 字节码增强 │
│ 接口要求 │ 必须有接口 │ 不需要接口 │
│ 性能 │ 反射调用,早期略慢 │ 字节码直接调用,性能更好 │
│ final 方法 │ 不受影响(接口无 final) │ 不能代理(final 方法不可重写) │
│ 创建成本 │ 低(只生成一个类) │ 高(需要生成字节码) │
│ 调用成本 │ 较高(反射) │ 较低(FastClass 机制) │
│ Spring 默认 │ 有接口时使用 │ 无接口时使用 │
└──────────────┴──────────────────────┴──────────────────────────────┘
*/4.3 生产级实现
4.3.1 Spring AOP 原理(重点)
Spring AOP 是代理模式最经典的工业级应用。其核心原理:在 Bean 初始化时,通过 BeanPostProcessor 创建代理对象替代原始 Bean。
java
// ============ Spring AOP 底层原理模拟 ============
// 1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Loggable {
String value() default "";
}
// 2. 切面逻辑(Advice)
class LoggingAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object[] args = invocation.getArguments();
System.out.println("[AOP-BEFORE] " + methodName + " 参数: " + Arrays.toString(args));
long start = System.currentTimeMillis();
try {
Object result = invocation.proceed(); // 执行目标方法
System.out.println("[AOP-AFTER] " + methodName + " 耗时: " +
(System.currentTimeMillis() - start) + "ms");
return result;
} catch (Exception e) {
System.out.println("[AOP-EXCEPTION] " + methodName + " " + e.getMessage());
throw e;
}
}
}
// 3. 模拟 Spring 的代理创建(BeanPostProcessor)
class AopProxyCreator {
// 判断是否需要创建代理
public static boolean shouldProxy(Class<?> clazz) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Loggable.class)) {
return true;
}
}
return false;
}
// 创建代理对象
public static Object createProxy(Object target) {
Class<?> clazz = target.getClass();
if (clazz.getInterfaces().length > 0) {
// 有接口:使用 JDK 动态代理
return Proxy.newProxyInstance(
clazz.getClassLoader(),
clazz.getInterfaces(),
(proxy, method, args) -> {
// 检查方法是否有 @Loggable 注解
Method targetMethod = clazz.getMethod(method.getName(),
method.getParameterTypes());
if (targetMethod.isAnnotationPresent(Loggable.class)) {
System.out.println("[AOP] 拦截: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("[AOP] " + method.getName() +
" 耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
return method.invoke(target, args);
}
);
} else {
// 无接口:使用 CGLIB 代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new org.springframework.cglib.proxy.MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
Method targetMethod = clazz.getMethod(method.getName(),
method.getParameterTypes());
if (targetMethod.isAnnotationPresent(Loggable.class)) {
System.out.println("[AOP] 拦截: " + method.getName());
long start = System.currentTimeMillis();
Object result = proxy.invokeSuper(obj, args);
System.out.println("[AOP] " + method.getName() +
" 耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
return proxy.invokeSuper(obj, args);
}
});
return enhancer.create();
}
}
}4.3.2 Spring Boot 实际 AOP 应用
java
// ============ 1. 自定义注解 ============
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface RateLimit {
int value() default 10; // 每秒允许的请求数
int timeout() default 1; // 超时时间(秒)
}
// ============ 2. 切面 ============
@Aspect
@Component
class RateLimitAspect {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
String key = point.getSignature().toShortString();
RateLimiter limiter = limiters.computeIfAbsent(key,
k -> RateLimiter.create(rateLimit.value()));
if (limiter.tryAcquire(rateLimit.timeout(), TimeUnit.SECONDS)) {
return point.proceed();
} else {
throw new RuntimeException("请求过于频繁,请稍后重试");
}
}
}
// ============ 3. 使用 ============
@Service
class ApiService {
@RateLimit(value = 5) // 每秒最多 5 次调用
public String queryData(String param) {
return "data: " + param;
}
}
// ============ 4. Spring AOP 核心流程图 ============
/*
* Spring 容器启动
* │
* ▼
* Bean 实例化 (new ApiService())
* │
* ▼
* BeanPostProcessor.postProcessAfterInitialization()
* │
* ├── 检查 Bean 是否需要代理
* │ (是否有 @Aspect 匹配的切点)
* │
* ├── 需要代理
* │ ├── 有接口 → JDK 动态代理
* │ └── 无接口 → CGLIB 代理
* │
* ▼
* 返回代理对象(替换原始 Bean)
* │
* ▼
* 客户端调用 Bean 方法
* │
* ▼
* 代理对象拦截 → 执行 Advice 链 → 调用目标方法 → 返回结果
*/4.3.3 远程代理(RPC 调用)
java
// ============ 远程代理:本地代理远程服务 ============
// 服务接口
interface OrderRpcService {
OrderDTO getOrder(Long orderId);
List<OrderDTO> listOrders(Long userId);
}
// 远程代理(RPC 代理)
class OrderRpcProxy implements OrderRpcService {
private String remoteUrl;
private RestTemplate restTemplate;
public OrderRpcProxy(String remoteUrl) {
this.remoteUrl = remoteUrl;
this.restTemplate = new RestTemplate();
}
@Override
public OrderDTO getOrder(Long orderId) {
// 封装远程调用细节
String url = remoteUrl + "/api/orders/" + orderId;
System.out.println("[RPC] 远程调用: GET " + url);
try {
return restTemplate.getForObject(url, OrderDTO.class);
} catch (Exception e) {
throw new RuntimeException("RPC 调用失败: " + url, e);
}
}
@Override
public List<OrderDTO> listOrders(Long userId) {
String url = remoteUrl + "/api/orders?userId=" + userId;
System.out.println("[RPC] 远程调用: GET " + url);
try {
OrderDTO[] orders = restTemplate.getForObject(url, OrderDTO[].class);
return orders != null ? Arrays.asList(orders) : Collections.emptyList();
} catch (Exception e) {
throw new RuntimeException("RPC 调用失败: " + url, e);
}
}
}
// 客户端使用
// OrderRpcService orderService = new OrderRpcProxy("http://order-service:8080");
// OrderDTO order = orderService.getOrder(123L);
// 客户端完全不知道这是远程调用!4.3.4 缓存代理
java
// ============ 缓存代理:为耗时操作添加缓存 ============
class CachingProxy implements UserService {
private UserService delegate;
private Map<Long, User> cache = new ConcurrentHashMap<>();
public CachingProxy(UserService delegate) {
this.delegate = delegate;
}
@Override
public User getUser(Long id) {
// 先从缓存获取
User cached = cache.get(id);
if (cached != null) {
System.out.println("[缓存命中] userId=" + id);
return cached;
}
// 缓存未命中,调用真实服务
User user = delegate.getUser(id);
cache.put(id, user);
System.out.println("[缓存更新] userId=" + id);
return user;
}
@Override
public void saveUser(User user) {
delegate.saveUser(user);
// 更新缓存
cache.put(user.getId(), user);
}
@Override
public void deleteUser(Long id) {
delegate.deleteUser(id);
// 清除缓存
cache.remove(id);
}
}五、优缺点
优点
| 优点 | 说明 |
|---|---|
| 职责清晰 | 真实对象专注业务,代理对象处理横切关注点 |
| 高扩展性 | 无需修改目标对象即可添加功能 |
| 智能化 | 可以添加访问控制、延迟加载、缓存等智能逻辑 |
| 解耦 | 客户端与真实对象解耦,代理可以透明替换真实对象 |
缺点
| 缺点 | 说明 |
|---|---|
| 性能开销 | 多一层代理调用,有轻微性能损失 |
| 复杂度增加 | 引入代理类,系统复杂度增加 |
| 调试困难 | 代理链较长时,调用栈深,排查问题困难 |
| CGLIB 限制 | 不能代理 final 类和 final 方法,代理类不能是 final |
六、适用场景
- AOP(面向切面编程):日志、事务、权限、缓存等横切关注点。
- 延迟加载:大对象(如图片、报表)的按需加载。
- 访问控制:权限校验、防火墙代理。
- 远程代理:RPC、RMI、gRPC 等远程服务调用。
- 缓存代理:为耗时操作添加缓存层。
- 虚拟代理:为创建开销大的对象提供占位符。
- 保护代理:控制对敏感对象的访问。
七、JDK / Spring 框架中的实际应用
7.1 JDK 中的代理
java
// 1. java.lang.reflect.Proxy —— JDK 动态代理核心类
// 2. java.rmi.* —— RMI 远程代理
// 3. Collections.unmodifiableList() —— 保护代理(禁止修改)
List<String> list = Collections.unmodifiableList(new ArrayList<>());
// 4. Collections.synchronizedList() —— 同步代理
List<String> syncList = Collections.synchronizedList(new ArrayList<>());7.2 Spring 中的代理(核心)
java
// 1. Spring AOP 全面基于代理模式
// @Transactional → 事务代理
// @Cacheable → 缓存代理
// @Async → 异步代理
// @Retryable → 重试代理
// @PreAuthorize → 安全代理
// 2. Spring 的代理创建策略
// spring.aop.proxy-target-class=true → 强制使用 CGLIB
// spring.aop.proxy-target-class=false → 有接口用 JDK,无接口用 CGLIB
// 3. MyBatis Mapper 代理
// MapperProxy 实现了 InvocationHandler
// userMapper.getUser(1L) → MapperProxy.invoke() → SQL 执行7.3 Spring 事务代理原理
java
// @Transactional 注解的工作原理(简化版)
@Service
class OrderService {
@Transactional
public void createOrder(Order order) {
// Spring 实际上创建了代理对象:
// OrderService$$EnhancerBySpringCGLIB extends OrderService {
// TransactionInterceptor 拦截 createOrder() {
// beginTransaction();
// try {
// super.createOrder(order); // 调用真实方法
// commit();
// } catch (Exception e) {
// rollback();
// throw e;
// }
// }
// }
}
}八、与其他模式的关系
| 模式 | 关系 |
|---|---|
| 装饰器模式 | 结构几乎相同,但意图不同:代理控制访问,装饰器增强功能。代理通常由框架自动生成,装饰器由客户端显式组装。 |
| 适配器模式 | 代理和原对象实现相同接口,适配器和目标对象接口不同。 |
| 外观模式 | 外观为复杂子系统提供统一入口,代理为单个对象提供访问控制。外观可以包含多个对象,代理只代理一个对象。 |
| 享元模式 | 享元模式中的工厂可以返回代理对象,实现延迟加载。 |
九、面试常见问题
Q1:JDK 动态代理和 CGLIB 代理有什么区别?(高频)
答:
- JDK 动态代理基于反射+接口,通过
Proxy.newProxyInstance()生成$Proxy类,目标类必须实现接口。优点是 JDK 原生支持,无需额外依赖;缺点是只能代理接口方法。 - CGLIB 代理基于继承+字节码增强,通过
Enhancer生成目标类的子类作为代理。优点是不需要接口,性能更好(FastClass 机制);缺点是不能代理final类和方法。 - Spring 默认策略:有接口用 JDK,无接口用 CGLIB。Spring Boot 2.x 默认使用 CGLIB(
proxy-target-class=true)。
Q2:Spring AOP 的原理是什么?(高频)
答:Spring AOP 基于动态代理实现。在 Bean 初始化时,BeanPostProcessor 检查 Bean 是否需要被代理(是否有切面匹配)。如果需要,则创建代理对象替代原始 Bean:
- 如果目标类实现了接口,默认使用 JDK 动态代理。
- 如果目标类没有接口,使用 CGLIB 代理。
- 方法调用时,代理对象拦截调用,执行 Advice 链(Before → 目标方法 → AfterReturning/AfterThrowing → After),最后返回结果。
Q3:为什么 @Transactional 注解在同一个类中自调用会失效?(高频)
答:因为 Spring AOP 基于代理,@Transactional 是通过代理对象拦截来实现的。当在同一个类中调用 this.methodB() 时,this 指向的是目标对象而不是代理对象,因此不会被代理拦截,事务注解失效。
解决方案:
- 通过
AopContext.currentProxy()获取代理对象后调用。 - 将方法拆分到不同的 Service 中。
- 在启动类上添加
@EnableAspectJAutoProxy(exposeProxy = true)。
Q4:静态代理和动态代理有什么区别?
答:
- 静态代理:代理类在编译时就已确定,需要手动编写代理类。一个代理类只能代理一个接口,代码冗余。
- 动态代理:代理类在运行时动态生成,无需手动编写代理类。一个
InvocationHandler可以代理多个接口,复用性高。 - 动态代理的优势:减少代码量,统一处理横切逻辑,运行时动态切换。
Q5:Spring 中哪些场景使用了代理模式?
答:
- AOP:
@Transactional、@Cacheable、@Async、@Retryable等。 - MyBatis Mapper:
MapperProxy代理 Mapper 接口,自动生成 SQL 执行逻辑。 - Spring Security:
@PreAuthorize、@Secured通过代理实现方法级安全控制。 - Spring Data JPA:
JpaRepository的代理实现,自动生成 CRUD 方法。 - Spring Cloud Feign:
FeignClient接口的代理,封装 HTTP 远程调用。
Q6:CGLIB 代理为什么不能代理 final 方法?
答:CGLIB 通过继承目标类来生成代理,Java 中 final 方法不能被重写,因此 CGLIB 无法覆盖 final 方法来添加代理逻辑。同理,final 类不能被继承,也不能被 CGLIB 代理。private 方法同样不能被代理(不可见)。
Q7:如何查看 Spring 生成的代理类?
答:
- 打印
bean.getClass().getName(),CGLIB 代理类名包含$$EnhancerBySpringCGLIB。 - 在
application.yml中设置logging.level.org.springframework.aop=DEBUG。 - 可以使用
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib")将 CGLIB 生成的字节码输出到文件。
