代理模式--设计模式

代理模式允许在不修改源代码的情况下对目标对象进行功能扩展。静态代理中,代理类和真实角色是明确的,而动态代理则在运行时生成。文章通过租房的例子展示了静态代理的实现,包括接口、真实角色、代理角色和客户端的交互。代理模式的好处在于解耦和便于扩展公共业务。

Java代理模式深度解析(静态+动态+Spring AOP底层)

代理模式是结构型设计模式的核心,核心价值是在不修改目标对象源码的前提下,对其功能进行增强或控制访问。它通过引入“代理对象”作为中间层,隔离客户端与目标对象,实现业务分工与功能扩展,也是Spring AOP(面向切面编程)的底层核心原理。本文从静态代理、动态代理(JDK/CGLIB)、应用场景三个维度,结合实战案例详解代理模式,帮你快速掌握其设计思想与落地方式。

一、代理模式的核心原理

1. 设计思想

代理模式的本质是“控制与增强”——代理对象持有目标对象的引用,客户端通过调用代理对象的方法,间接访问目标对象;代理对象在转发请求前后,可添加额外逻辑(如日志、权限校验、事务管理),实现无侵入式扩展。

2. 三大核心角色

代理模式的结构固定,由三个角色构成闭环:

角色名称核心职责
Subject(抽象主题)定义目标对象与代理对象的共同接口/抽象类,确保代理对象与目标对象类型一致,客户端可统一调用
RealSubject(真实主题)被代理的目标对象,实现抽象主题接口,包含核心业务逻辑(如“房东出租房子”)
Proxy(代理对象)持有真实主题的引用,实现抽象主题接口,转发客户端请求给目标对象,同时添加额外功能(如“中介带看房”)

3. 核心价值

  • 解耦业务逻辑:目标对象专注核心业务(如房东只关注出租),公共功能(如看房、签约)交给代理对象,实现分工;
  • 无侵入扩展:不修改目标对象源码,通过代理添加新功能(如日志、缓存、权限),符合开闭原则;
  • 控制访问权限:代理对象可拦截客户端请求,实现权限校验、延迟加载、限流等控制逻辑;
  • 支撑框架底层:Spring AOP、RPC框架(如Dubbo)、缓存框架等均基于代理模式实现。

二、静态代理(手动实现,简单直观)

静态代理是指代理类在编译期就已生成,与目标对象一一对应,适合目标类少、功能固定的场景。以“租房”为场景,演示静态代理的完整实现。

1. 步骤1:定义抽象主题(Subject)

创建租房的统一接口,规范目标对象(房东)与代理对象(中介)的核心方法:

// 抽象主题:租房接口(定义核心业务方法)
public interface Rent {
    /** 核心业务:出租房子 */
    void rent();
}

2. 步骤2:实现真实主题(RealSubject)

创建房东类,实现抽象主题接口,专注核心业务逻辑:

// 真实主题:房东(被代理对象,仅关注核心业务)
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东:房屋出租,每月3000元");
    }
}

3. 步骤3:实现代理对象(Proxy)

创建中介类,持有房东引用,实现租房接口,在转发请求前后添加额外功能:

// 代理对象:中介(代理房东,添加额外服务)
public class RentProxy implements Rent {
    // 持有目标对象(房东)的引用
    private Host host;

    // 构造器注入目标对象
    public RentProxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent() {
        // 代理前:额外服务1 - 带客户看房
        seeHouse();
        // 转发请求:调用目标对象的核心业务
        host.rent();
        // 代理后:额外服务2 - 签订租房合同
        signContract();
        // 代理后:额外服务3 - 收取中介费
        collectFee();
    }

    // 代理类的额外功能:带看房
    private void seeHouse() {
        System.out.println("中介:带客户实地看房,介绍房屋详情");
    }

    // 代理类的额外功能:签订合同
    private void signContract() {
        System.out.println("中介:协助签订租房合同");
    }

    // 代理类的额外功能:收取中介费
    private void collectFee() {
        System.out.println("中介:收取月租金50%的中介费");
    }
}

4. 步骤4:客户端调用(Client)

客户端通过代理对象(中介)访问目标对象(房东),无需直接与房东交互:

// 客户端:租房的客户
public class Client {
    public static void main(String[] args) {
        // 1. 创建目标对象(房东)
        Host host = new Host();
        // 2. 创建代理对象(中介),关联房东
        RentProxy proxy = new RentProxy(host);
        // 3. 客户端调用代理对象的方法
        proxy.rent();
    }
}

5. 运行结果

中介:带客户实地看房,介绍房屋详情
房东:房屋出租,每月3000元
中介:协助签订租房合同
中介:收取月租金50%的中介费

6. 静态代理的优缺点

  • 优点
    • 实现简单,逻辑清晰,无需依赖框架;
    • 无侵入式扩展,不修改目标对象源码;
    • 业务分工明确,目标对象专注核心逻辑。
  • 缺点
    • 代码冗余:一个目标对象对应一个代理类,目标类增多时会导致“类爆炸”;
    • 维护成本高:目标接口新增方法时,所有代理类需同步修改;
    • 灵活性差:代理类编译期固定,无法动态切换代理逻辑。

三、动态代理(运行时生成,灵活高效)

动态代理是指代理类在运行时通过反射机制动态生成,无需手动编写代理类,可适配多个目标对象,是实际开发中的主流方案。Java中动态代理主要分为两种:JDK动态代理(基于接口)和CGLIB动态代理(基于继承)。

1. JDK动态代理(基于接口,JDK原生支持)

核心原理

JDK动态代理通过java.lang.reflect.Proxy类和InvocationHandler接口实现,要求目标对象必须实现接口(代理类动态实现该接口)。

实战案例:通用日志代理

创建一个通用的日志代理,为任意实现接口的目标对象添加日志记录功能:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 通用InvocationHandler:封装代理逻辑(日志记录)
public class LogInvocationHandler implements InvocationHandler {
    // 持有目标对象(任意实现接口的对象)
    private Object target;

    // 注入目标对象
    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 代理逻辑核心方法:所有代理对象的方法调用都会转发到这里
     * @param proxy 代理对象本身
     * @param method 目标对象的方法
     * @param args 方法参数
     * @return 方法返回值
     */
    @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("=== 日志:方法" + method.getName() + "执行结束 ===");
        return result;
    }

    // 2. 生成代理对象的工具方法
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(), // 目标对象的类加载器
            target.getClass().getInterfaces(), // 目标对象实现的接口
            new LogInvocationHandler(target)   // 代理逻辑处理器
        );
    }
}

// 3. 测试:给租房业务添加日志代理
public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象(房东)
        Rent host = new Host();
        // 2. 动态生成代理对象(日志+租房代理)
        Rent proxy = (Rent) LogInvocationHandler.getProxy(host);
        // 3. 调用代理对象方法
        proxy.rent();
    }
}
运行结果
=== 日志:方法rent开始执行 ===
房东:房屋出租,每月3000元
=== 日志:方法rent执行结束 ===
核心特点
  • 基于接口实现,目标对象必须实现接口;
  • 代理类由JDK动态生成,无需手动编写;
  • 灵活通用,一个代理逻辑可适配多个目标对象;
  • 底层通过反射实现,性能略低于CGLIB。

2. CGLIB动态代理(基于继承,第三方库)

核心原理

CGLIB(Code Generation Library)是第三方字节码生成库,通过继承目标类动态生成代理类(无需目标类实现接口),底层基于ASM框架修改字节码。Spring AOP默认优先使用JDK动态代理,若目标类无接口则使用CGLIB。

实战案例:给无接口的类添加代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 1. 无接口的目标类(房东)
public class HostNoInterface {
    public void rent() {
        System.out.println("无接口房东:房屋出租,每月3000元");
    }
}

// 2. CGLIB代理逻辑处理器
public class CglibProxy implements MethodInterceptor {
    // 持有目标对象
    private Object target;

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

    // 生成代理对象
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
        enhancer.setCallback(this); // 设置代理逻辑处理器
        return enhancer.create(); // 生成代理对象
    }

    /**
     * 代理逻辑核心方法
     * @param o 代理对象
     * @param method 目标方法
     * @param args 方法参数
     * @param methodProxy 方法代理对象
     * @return 方法返回值
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 代理前:带看房
        System.out.println("CGLIB代理:带客户看房");
        // 转发请求:调用目标对象方法
        Object result = method.invoke(target, args);
        // 代理后:收取中介费
        System.out.println("CGLIB代理:收取中介费");
        return result;
    }
}

// 3. 测试类
public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        HostNoInterface host = new HostNoInterface();
        // 2. 生成CGLIB代理对象
        HostNoInterface proxy = (HostNoInterface) new CglibProxy(host).getProxy();
        // 3. 调用代理对象方法
        proxy.rent();
    }
}
运行结果
CGLIB代理:带客户看房
无接口房东:房屋出租,每月3000元
CGLIB代理:收取中介费
核心特点
  • 基于继承实现,目标对象无需实现接口;
  • 代理类是目标类的子类,通过重写方法实现代理;
  • 性能优于JDK动态代理(直接操作字节码);
  • 无法代理final方法(final方法不能被重写)。

3. 静态代理 vs 动态代理

对比维度静态代理动态代理(JDK/CGLIB)
代理类生成时机编译期手动编写运行时动态生成
目标类要求可实现接口,也可无接口JDK:需实现接口;CGLIB:无要求
灵活性差(一个目标类对应一个代理类)高(一个代理逻辑适配多个目标类)
维护成本高(接口新增方法需同步修改代理类)低(无需修改代理逻辑)
性能高(无反射开销)JDK:中等;CGLIB:高
适用场景目标类少、功能固定的简单场景目标类多、功能灵活扩展的复杂场景

四、代理模式的实际应用场景

1. 框架底层应用

  • Spring AOP:通过动态代理实现切面逻辑(日志、事务、权限),目标对象为业务Bean,代理对象为增强后的Bean;
  • RPC框架(Dubbo):客户端通过动态代理生成服务接口的代理对象,实现远程调用的透明化;
  • MyBatis:Mapper接口的代理对象由MyBatis动态生成,实现SQL语句的执行;
  • 缓存框架(Spring Cache):通过代理对象拦截方法调用,实现缓存的自动存取。

2. 实际开发应用

  • 日志记录:代理核心业务方法,记录请求参数、响应结果、执行时间;
  • 权限校验:代理需要权限的接口,校验用户是否有权限访问;
  • 事务管理:代理数据库操作方法,实现事务的开启、提交、回滚;
  • 延迟加载:代理重量级对象(如数据库连接),需要时才初始化;
  • 限流熔断:代理高频接口,实现限流(限制请求次数)、熔断(服务故障时降级)。

五、代理模式与装饰器模式的区别(面试高频)

两者均通过“组合”实现功能扩展,容易混淆,核心区别在于设计目标

对比维度代理模式装饰器模式
核心目标控制目标对象的访问(如权限、日志)增强目标对象的功能(如添加新服务)
角色定位代理对象与目标对象是“委托关系”装饰器与目标对象是“增强关系”
客户端感知客户端通常不知道目标对象的存在客户端明确知道是在增强目标对象
功能叠加一般单一代理逻辑,不支持多层嵌套支持多层装饰,功能叠加(如咖啡+牛奶+糖)
适用场景访问控制、日志、事务、RPC功能动态扩展、组合

六、总结

代理模式的核心是“控制与增强”,通过代理对象隔离客户端与目标对象,实现无侵入式扩展:

  1. 静态代理简单直观,适合简单场景,但灵活性差;
  2. 动态代理(JDK/CGLIB)灵活高效,是框架与复杂场景的首选;
  3. 实际开发中,动态代理应用更广(如Spring AOP、RPC),需重点掌握;
  4. 区分代理模式与装饰器模式的关键:代理侧重“控制访问”,装饰器侧重“增强功能”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rsun04551

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值