Java 编程之代理模式

前言

代理模式(Proxy Pattern)是 Java 设计模式中的经典结构型模式,常用于控制对象访问,增强功能,延迟加载等场景。本篇将由浅入深,详细解析静态代理、动态代理、JDK 与 CGLIB 实现,以及实战应用和常见误区。

一、什么是代理模式?

代理模式:为其他对象提供一种代理以控制对这个对象的访问。

好比你想访问一个资源,但并不直接去访问,而是通过"中间人"来帮你完成,这个"中间人"可以做一些增强处理,比如权限检查、记录日志、懒加载等。

使用场景列举

  • 控制对象访问权限(如防火墙代理)

  • 添加额外功能(如记录日志、监控耗时)

  • 延迟加载(如大型图片加载)

  • RPC 框架远程调用(如 Dubbo)

  • Spring AOP 实现原理

代理模式UML图示

综合静态代理、JDK 代理、CGLIB代理、Spring AOP代理如下:

本篇将基于此图,综合分析并代码示例改图的代理模式.

二、静态代理(Static Proxy)

你想去看一场电影,但自己太忙,于是打电话让朋友帮你买:

"小张,帮我买张电影票,如果时间太晚就别买了。"

朋友(代理)可以在你不知道的情况下,帮你加上"时间检查"的功能 ------ 这就是静态代理的"前后增强"。

1. 定义接口

java 复制代码
// 电影票服务接口(被代理的核心功能)
public interface TicketService {
    void buyTicket();
}

2. 真实对象

java 复制代码
// 真实用户类(实际执行买票操作)
public class User implements TicketService {
    @Override
    public void buyTicket() {
        System.out.println("🎟️ 成功购买电影票!");
    }
}

3. 代理对象

java 复制代码
import java.time.LocalTime;

// 朋友代理类(添加时间检查功能)
public class FriendProxy implements TicketService {
    private final TicketService realUser; // 持有真实对象的引用

    public FriendProxy(TicketService realUser) {
        this.realUser = realUser;
    }

    @Override
    public void buyTicket() {
        // 前增强:时间检查
        if (checkTime()) {
            System.out.println("🕒 当前时间允许购票");
            realUser.buyTicket(); // 调用真实对象的方法
        } else {
            System.out.println("⏰ 当前时间已晚,停止购票");
        }
    }

    // 私有方法实现具体增强逻辑
    private boolean checkTime() {
        LocalTime now = LocalTime.now();
        return !now.isAfter(LocalTime.of(22, 0)); // 22点后禁止购票
    }
}

4. 客户端调用

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        TicketService user = new User();
        
        // 创建代理对象(包装真实对象)
        TicketService friendProxy = new FriendProxy(user);
        
        System.out.println("=== 场景1:21:30购票 ===");
        setTestTime(21, 30);
        friendProxy.buyTicket();

        System.out.println("\n=== 场景2:22:30购票 ===");
        setTestTime(22, 30);
        friendProxy.buyTicket();
    }

    // 测试辅助方法:模拟设置时间
    private static void setTestTime(int hour, int minute) {
        // 实际开发中应使用真实时间,此处仅为演示
        System.out.println("🕒 模拟系统时间:" + 
            String.format("%02d:%02d", hour, minute));
    }
}

特点总结

  • 编译期就确定代理类

  • 代理类与真实类实现同一接口

  • 缺点:为每个目标类写一个代理类,代码臃肿

三、动态代理(JDK Proxy)

代购平台不关心你具体买什么票,只提供"通用服务":你输入需求,它自动派人帮你处理,甚至可以"插入"下单前的优惠券、付款提醒等逻辑。这类平台通过"接口"来适配不同的商品、服务 ------ 正如 JDK 动态代理一样:通过接口适配多个真实对象,运行时生成代理对象

步骤:

1. 定义通用服务接口

java 复制代码
// 通用购买服务接口(动态代理的核心适配点)
public interface PurchaseService {
    void buy(String item);
}

2. 创建多个真实服务实现

java 复制代码
// 电影票购买服务
public class TicketService implements PurchaseService {
    @Override
    public void buy(String item) {
        System.out.println("🎟️ 正在购买 " + item);
    }
}

// 图书购买服务
public class BookService implements PurchaseService {
    @Override
    public void buy(String item) {
        System.out.println("📚 正在购买 " + item);
    }
}

3. 创建动态代理处理器(核心增强逻辑)

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PurchaseProxyHandler implements InvocationHandler {
    private final Object target; // 动态绑定的真实对象

    public PurchaseProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前增强:优惠券检查
        checkCoupon();
        
        // 执行真实对象的方法
        Object result = method.invoke(target, args);
        
        // 后增强:付款提醒
        paymentReminder(method.getName());
        
        return result;
    }

    private void checkCoupon() {
        System.out.println("🎟️ 检查可用优惠券...");
        // 实际开发中可接入优惠券系统
        System.out.println("✅ 发现满50减10优惠券,已自动应用!");
    }

    private void paymentReminder(String methodName) {
        System.out.println("💳 即将发起支付,请确保账户余额充足");
        System.out.println("🔔 交易提醒:" + methodName + " 操作已完成");
    }
}

4.创建代理工厂(运行时生成代理)

java 复制代码
import java.lang.reflect.Proxy;

public class ProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new PurchaseProxyHandler(target)
        );
    }
}

5. 客户端调用演示

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        PurchaseService ticketService = new TicketService();
        PurchaseService bookService = new BookService();

        // 动态生成代理对象(运行时绑定)
        PurchaseService ticketProxy = ProxyFactory.createProxy(ticketService);
        PurchaseService bookProxy = ProxyFactory.createProxy(bookService);

        System.out.println("=== 场景1:购买电影票 ===");
        ticketProxy.buy("《速度与激情10》电影票");

        System.out.println("\n=== 场景2:购买技术书籍 ===");
        bookProxy.buy("《Java核心编程》");
    }
}

6.运行结果演示

四、CGLIB 动态代理(继承方式)

你弟弟长得像你,让他冒充你去电影院排队买票。他可以复用你的一切行为,还能做一些你平时不做的事,比如买爆米花顺手打卡。这是 CGLIB 动态代理 ------ 不依赖接口,而是继承原类进行增强

JDK 动态代理只能代理接口,而 CGLIB 可以代理没有接口的类,通过继承方式实现。

1. 添加CGLIB依赖(Maven配置)

xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 创建目标类(无接口)

java 复制代码
// 真实用户类(无需实现任何接口)
public class User {
    public void buyTicket() {
        System.out.println("🎟️ 正在使用本人身份购买电影票");
    }
 
    private void secretAction() {
        System.out.println("(这是只有本人能做的私密操作)");
    }
}

3. 创建CGLIB代理处理器

java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
public class CglibProxyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        // 前增强:冒充检查
        if (checkImpersonation()) {
            System.out.println("🎭 弟弟正在冒充身份...");
            
            // 执行目标方法(通过MethodProxy保证子类调用)
            Object result = proxy.invokeSuper(obj, args);
            
            // 后增强:附加操作
            buySnacks();
            clockIn();
            
            return result;
        }
        return null;
    }
 
    private boolean checkImpersonation() {
        System.out.println("🔍 验证身份信息...");
        // 实际开发中可接入人脸识别等验证逻辑
        return true;
    }
 
    private void buySnacks() {
        System.out.println("🍿 顺手购买大份爆米花");
    }
 
    private void clockIn() {
        System.out.println("📍 完成影院打卡任务");
    }
}

4. 创建代理工厂

java 复制代码
import net.sf.cglib.proxy.Enhancer;
 
public class CglibProxyFactory {
    public static <T> T createProxy(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass); // 设置目标类为父类
        enhancer.setCallback(new CglibProxyHandler());
        return (T) enhancer.create();
    }
}

5. 客户端调用演示

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建原始对象
        User realUser = new User();
        
        // 动态生成代理对象(继承自User)
        User proxyUser = CglibProxyFactory.createProxy(User.class);
 
        System.out.println("=== 场景1:正常购票 ===");
        proxyUser.buyTicket();
 
        System.out.println("\n=== 场景2:尝试调用私密方法 ===");
        try {
            proxyUser.secretAction();
        } catch (Exception e) {
            System.out.println("❗ 代理无法访问私有方法:" + e.getMessage());
        }
    }
}

6.运行结果演示

五、JDK vs CGLIB 区别对比

特性 JDK 动态代理 CGLIB 动态代理
代理方式 接口 继承类
是否要求实现接口
性能 JDK 在 Java8 之后更优 CGLIB 在方法调用多时更优
生成原理 Proxy + 反射 ASM字节码增强
Spring AOP 默认 接口用 JDK,否则用 CGLIB 接口优先使用 JDK

六、Spring AOP 基于代理实现

你佩戴了一副智能眼镜,它会在你每次看片前自动记录日志、在过长时间时提示你休息,还能阻止你观看某些类型的影片。这正是 Spring AOP 的理念 ------ 在不改变原始业务逻辑的前提下,通过代理注入增强功能

1. 创建核心业务接口与实现

java 复制代码
// 影片服务接口
public interface MovieService {
    void watchMovie(String movieType);
}
 
// 用户观影实现类
@Component
public class UserMovieService implements MovieService {
    @Override
    public void watchMovie(String movieType) {
        System.out.println("🎬 正在观看《黑客帝国》 - 类型:" + movieType);
    }
}

2. 定义AOP增强切面

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
 
@Aspect
@Component
public class SmartGlassesAspect {
 
    // 前置通知:观看日志记录
    @Before("execution(* com.example.service.MovieService.watchMovie(..))")
    public void logWatchStart() {
        System.out.println("📅 [" + java.time.LocalTime.now() + "] 开始观影记录");
    }
 
    // 后置通知:健康提醒
    @After("execution(* com.example.service.MovieService.watchMovie(..))")
    public void healthReminder() {
        System.out.println("👁️ 持续观看45分钟,建议远眺休息!");
    }
 
    // 环绕通知:内容过滤(核心增强)
    @Around("execution(* com.example.service.MovieService.watchMovie(..)) && args(movieType)")
    public Object contentFilter(ProceedingJoinPoint joinPoint, String movieType) throws Throwable {
        // 类型检查
        if (isRestrictedType(movieType)) {
            System.out.println("⛔ 检测到限制级内容,已阻止播放!");
            throw new SecurityException("内容访问被拒绝");
        }
 
        // 执行原始方法
        System.out.println("🔍 智能眼镜正在进行内容安全扫描...");
        Object result = joinPoint.proceed();
 
        // 播放后处理
        System.out.println("🎥 影片播放完毕,自动记录观看历史");
        return result;
    }
 
    private boolean isRestrictedType(String type) {
        return "horror".equalsIgnoreCase(type) || "adult".equalsIgnoreCase(type);
    }
}

3. Spring配置类(启用AOP)

java 复制代码
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy // 关键注解:启用AOP自动代理
public class AppConfig {
}

4. 客户端测试类

java 复制代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class Client {
    public static void main(String[] args) {
        // 初始化Spring容器
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(AppConfig.class);
 
        // 获取代理对象(JDK/CGLIB自动选择)
        MovieService movieService = context.getBean(MovieService.class);
 
        System.out.println("=== 场景1:正常观影 ===");
        movieService.watchMovie("sci-fi");
 
        System.out.println("\n=== 场景2:观看限制级内容 ===");
        try {
            movieService.watchMovie("horror");
        } catch (Exception e) {
            System.out.println("❗ 错误处理:" + e.getMessage());
        }
 
        context.close();
    }
}

5.运行结果演示

七、实际开发中常见用途

用途 示例
AOP 日志、事务、权限
RPC 框架 Dubbo、gRPC
缓存代理 加入缓存处理逻辑
安全代理 权限校验、输入检查
延迟加载 图片、数据库懒加载

八、代理模式的优缺点

优点

  • 职责清晰,控制访问

  • 可插拔增强逻辑(如日志、缓存)

  • 解耦核心逻辑与扩展逻辑

缺点

  • 多层代理可能调试困难

  • 代码复杂性上升

  • 动态代理性能稍低(尤其频繁调用场景)

九、总结

Java 中的代理模式是一种强大而灵活的设计模式,通过代理对象来增强、控制或简化对象行为。掌握代理模式,特别是静态代理、JDK 动态代理、CGLIB 动态代理,不仅有助于理解 AOP、RPC 等技术的实现原理,也能帮助我们在日常编码中更好地组织和解耦代码逻辑。最后,用类比和思维导图来帮助记忆:
类比

  • 【静态代理】:请朋友帮你办事,还能顺带提醒你别忘带身份证。

  • 【JDK 代理】:用淘宝代购平台下单,平台接口决定代理谁执行。

  • 【CGLIB】:让弟弟假扮你去领快递,他还带回了饮料。

  • 【Spring AOP】:你佩戴智能眼镜看电影,自动记录与控制行为。
    总结

以下是代理模式相关信息的表格化呈现:

模块分类 子分类 详细说明
代理模式 分类 静态代理、动态代理(JDK/CGLIB)
实现方式 接口实现(JDK/静态代理)、类继承(CGLIB)
核心应用场景 AOP(日志/事务/安全)、RPC框架、缓存/权限控制、懒加载优化
Spring框架集成 AOP实现方式 @Aspect注解驱动切面、ProxyFactoryBean代理工厂
特性对比 优点 无侵入增强、业务解耦、细粒度控制、代码复用
缺点 调试复杂度增加、代理开销、学习曲线陡峭、对final类/方法限制(CGLIB)

其他

  1. 动态代理分支:

    • JDK动态代理:基于接口实现,通过InvocationHandler拦截
    • CGLIB动态代理:基于类继承实现,通过MethodInterceptor拦截,可代理无接口类
  2. AOP典型应用场景:

    • 分布式事务管理
    • 方法执行时间监控
    • 自定义权限校验
    • 接口调用日志审计
  3. 代理模式演进:

    静态代理 → JDK动态代理 → CGLIB → ASM字节码增强 → 编译期注解处理(如Lombok)

十、参考

《23种设计模式概览》

附本篇图语言:

@startuml

' 定义代理模式主类

class ProxyPattern {

+{static} main()

}

' 代理分类

package "代理分类" #DDDDDD {

class StaticProxy {

+invoke()

}

package "动态代理" #CCCCCC {

class JDKDynamicProxy {

+invoke()

}

复制代码
class CGLIBProxy {
  +intercept()
}

}

}

' 实现方式

package "实现方式" #FFAAAA {

interface TargetInterface {

+request()

}

class RealSubject {

+request()

}

class ProxySubject {

-realSubject: RealSubject

+request()

}

}

' 应用场景

package "应用场景" #AAFFAA {

class AOP {

+logging()

+transaction()

}

class RPC {

+remoteCall()

}

class CacheProxy {

+getData()

}

}

' Spring集成

package "Spring集成" #AAAAFF {

class AspectJ {

+@Before()

+@After()

}

class ProxyFactoryBean {

-target

-interceptorNames

}

}

' 关系定义

ProxyPattern --> StaticProxy : 使用

ProxyPattern --> JDKDynamicProxy : 使用

ProxyPattern --> CGLIBProxy : 使用

RealSubject ...|> TargetInterface : 实现

ProxySubject ...|> TargetInterface : 实现

ProxySubject o-- RealSubject : 委托

AOP --> ProxyPattern : 应用场景

RPC --> ProxyPattern : 应用场景

CacheProxy --> ProxyPattern : 应用场景

AspectJ --> ProxyPattern : 实现方式

ProxyFactoryBean --> ProxyPattern : 实现方式

' 优缺点标注

note right of ProxyPattern

优点:

  • 解耦调用方与实现方
  • 统一添加横切逻辑
  • 控制对象访问

缺点:

  • 增加系统复杂度
  • 调试难度提升
  • 性能轻微损耗
    end note

@enduml

相关推荐
我崽不熬夜7 分钟前
为什么你该立即学习 Java 的 Lambda 表达式?
java·后端·java ee
wsdchong之小马过河9 分钟前
2025虚幻引擎文件与文件夹命名规律
java·数据库·虚幻
小怡同学..11 分钟前
c++系列之智能指针的使用
开发语言·c++
幸运的大号暖贴14 分钟前
单点登录进阶:基于芋道(yudao)授权码模式的单点登录流程、代码实现与安全设计
java·安全
小白的代码日记26 分钟前
IDEA 中 Tomcat 部署 Java Web 项目(Maven 多模块 & 非 Maven 通用版)(linux+windows)
java·tomcat·intellij-idea
黑客飓风33 分钟前
JavaScript性能优化实战
开发语言·javascript·性能优化
coder_zh_1 小时前
Spring Boot自动配置原理
java·spring boot·spring
云博客-资源宝1 小时前
Android Manifest 权限描述大全
android·开发语言·php
晴空月明2 小时前
线程安全集合选择深度解析
java
是紫焅呢2 小时前
I排序算法.go
开发语言·后端·算法·golang·排序算法·学习方法·visual studio code