Java 编程之代理模式

发布于:2025-06-21 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言

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

一、什么是代理模式?

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

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

使用场景列举

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

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

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

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

  • Spring AOP 实现原理

代理模式UML图示

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

在这里插入图片描述
本篇将基于此图,综合分析并代码示例改图的代理模式.

二、静态代理(Static Proxy)

你想去看一场电影,但自己太忙,于是打电话让朋友帮你买:
“小张,帮我买张电影票,如果时间太晚就别买了。”
朋友(代理)可以在你不知道的情况下,帮你加上“时间检查”的功能 —— 这就是静态代理的“前后增强”。

1. 定义接口

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

2. 真实对象

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

3. 代理对象

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. 客户端调用

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. 定义通用服务接口

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

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

// 电影票购买服务
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. 创建动态代理处理器(核心增强逻辑)

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.创建代理工厂(运行时生成代理)

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. 客户端调用演示

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配置)

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

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

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

3. 创建CGLIB代理处理器

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. 创建代理工厂

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. 客户端调用演示

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. 创建核心业务接口与实现

// 影片服务接口
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增强切面

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)

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. 客户端测试类

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种设计模式概览》
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到