设计模式学习(14) 23-12 代理模式

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 静态代理](#3.1 静态代理)
      • [3.1.1 实现思路](#3.1.1 实现思路)
      • [3.1.2 UML类图](#3.1.2 UML类图)
      • [3.1.3 代码示例](#3.1.3 代码示例)
    • [3.2 动态代理-JDK代理](#3.2 动态代理-JDK代理)
      • [3.2.1 实现思路](#3.2.1 实现思路)
      • [3.2.2 UML类图](#3.2.2 UML类图)
      • [3.2.3 代码示例](#3.2.3 代码示例)
    • [3.3 动态代理-CGLIB代理](#3.3 动态代理-CGLIB代理)
      • [3.3.1 实现思路](#3.3.1 实现思路)
      • [3.3.2 UML类图](#3.3.2 UML类图)
      • [3.3.3 代码示例](#3.3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)

0.个人感悟

  • 说起代理模式,很容易想到现实生活中的中介。在客户和实际提供者之间增加了一层,最终还是完成了客户和提供者的交易,但可以做一些额外的动作,比如做服务(收费)等
  • 代理是实现方式有很多种,可以逐层演进来理解
    • 静态代理,自己去写代码实现,持有代理对象,在方法内做自己的额外动作,大家都能想到的方式
    • jdk对于这种场景进行了优化,提供了API,把通用模版提供好,提供了扩展类,我们自己填充业务代码就行,这里也体现了封装、多态的魅力。jdk代理在运行时内存中创建代理对象,不用像静态代理那样提前写代码;不过需要实现接口
    • Code Generation Library)是个功能强大、高性能、开源的代码生成库,代理是其中一个功能。对动态代理进一步进行扩展,它也是把通用模版准备好,听过API,我们只用实现自己的特异代码就行。不需要实现接口,也被称为子类代理。
  • 实际工作中多用spring aop,这个计划后面会专门整理spring学习笔记系列
  • 代理模式的uml图和装饰器模式有些像,但有本质区别:代 理控制访问,装饰器是增强功能

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

Provide a surrogate or placeholder for another object to control access to it.

中文翻译

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

理解

  • 代理模式是一种结构型设计模式,通过引入代理对象来控制对原始对象的访问
  • 代理对象在客户端和目标对象之间 起到中介作用,可以在不修改目标对象的情况下增加额外功能

2. 适配场景

2.1 适合的场景

  1. 远程访问:为远程对象提供本地代理(RPC、Web Service)
  2. 延迟加载:创建开销大的对象时,使用虚拟代理延迟实例化
  3. 访问控制:保护代理控制对敏感对象的访问权限
  4. 智能引用:在对象被访问时执行额外操作(计数、日志、缓存)
  5. 缓存代理:为频繁访问的结果提供缓存,提高性能
  6. 防火墙代理:保护网络资源,控制外部访问

2.2 常见场景举例

  • 图片加载:虚拟代理延迟加载大图片,先显示缩略图
  • 数据库连接池:连接代理管理连接的生命周期
  • Spring AOP:动态代理实现面向切面编程
  • VPN:网络访问的代理服务器
  • RMI(远程方法调用):Java的远程对象代理
  • MyBatis的Mapper接口:通过动态代理实现SQL映射

3. 实现方法

3.1 静态代理

3.1.1 实现思路

  1. 定义抽象主题接口:声明真实主题和代理的共同接口
  2. 实现真实主题类:定义实际执行业务逻辑的对象
  3. 创建代理类:实现主题接口,持有真实主题的引用,并添加额外功能
  4. 客户端通过代理访问:客户端调用代理对象的方法,代理再调用真实对象

3.1.2 UML类图

角色说明

  • Subject(抽象主题):定义真实主题和代理的共同接口
  • RealSubject(真实主题):实际执行业务逻辑的对象
  • Proxy(代理):持有真实主题的引用,控制对真实主题的访问
  • Client(客户端) :通过代理对象访问真实主题
    注意:
  • 代理和装饰者模式的区别:代理控制访问,装饰者增强功能

3.1.3 代码示例

suject接口和实现

java 复制代码
public interface ISubject {  
    /**  
     * @description 行为方法  
     * @author bigHao  
     * @date 2026/1/17  
     **/    
     void doAction();  
  
    /**  
     * @description 行为方法2  
     * @author bigHao  
     * @date 2026/1/17  
     **/    
     void doAction2();  
}

public class RealSubject implements ISubject {  
    @Override  
    public void doAction() {  
        System.out.println("do action");  
    }  
  
    @Override  
    public void doAction2() {  
        System.out.println("do acton2");  
    }  
}

静态代理

java 复制代码
public class SubjectProxy implements ISubject {  
    private ISubject subject;  
  
    public SubjectProxy() {  
        subject = new RealSubject();  
    }  
  
    @Override  
    public void doAction() {  
        // 代理行为  
        System.out.println("proxy acton start");  
  
        subject.doAction();  
  
        // 代理行为  
        System.out.println("proxy acton end");  
    }  
  
    @Override  
    public void doAction2() {  
        // 代理行为  
        System.out.println("proxy acton start");  
  
        subject.doAction2();  
  
        // 代理行为  
        System.out.println("proxy acton end");  
    }  
}

测试:

java 复制代码
public class Client {  
   static void main() {  
       ISubject proxy = new SubjectProxy();  
 
       System.out.println("=== proxy acton1 ===");  
       proxy.doAction();  
 
       System.out.println("=== proxy acton2 ===");  
       proxy.doAction2();  
   }  
}

输出:

复制代码
=== proxy acton1 ===
proxy acton start
do action
proxy acton end
=== proxy acton2 ===
proxy acton start
do acton2
proxy acton end

3.2 动态代理-JDK代理

3.2.1 实现思路

  1. 定义接口:与静态代理相同,需要抽象主题接口
  2. 实现真实主题:实现业务逻辑的真实类
  3. 实现InvocationHandler:编写调用处理器,定义代理的增强逻辑
  4. 使用Proxy.newProxyInstance创建代理:运行时动态生成代理类
  5. 客户端通过动态代理访问:客户端使用代理对象执行方法

3.2.2 UML类图

3.2.3 代码示例

java.lang.reflect提供了核心API,方法和参数说明:

java 复制代码
/**
 * Proxy类提供用于创建动态代理类和实例的静态方法。
 * 它是所有由Proxy创建的动态代理类的父类。
 */
public class Proxy implements java.io.Serializable {

/**
 * 
 * @param loade 定义代理类的类加载器。通常使用目标接口的类加载器,
 * 或者使用当前线程的上下文类加载器(Thread.currentThread().getContextClassLoader())
 *
 * @param interfaces 代理类要实现的接口列表
 *
 * @param h          调用处理器,当代理实例的方法被调用时,调用处理器的invoke方法会被调用
 *
 */
public static Object newProxyInstance(ClassLoader loader,  
                                      Class<?>[] interfaces,  
                                      InvocationHandler h)
                                      
}
java 复制代码
/**
* InvocationHandler是由代理实例的调用处理器实现的接口。
*/
public interface InvocationHandler {

   /**
     * 处理代理实例上的方法调用并返回结果。
     * 当在与之关联的代理实例上调用方法时,将在调用处理器上调用此方法。
     *
     * @param proxy  调用该方法的代理实例。
     *               这是代理对象本身,不是真实的目标对象。
     * 
     * @param method 对应于在代理实例上调用的接口方法的Method实例。
     *               Method对象的声明类将是在该方法声明的接口,
     *               该接口可能是代理类继承该方法的代理接口的超接口。
     * 
     * @param args   包含在代理实例的方法调用中传递的参数值的对象数组。
     *
     */
   public Object invoke(Object proxy, Method method, Object[] args)  
      throws Throwable;

}、

suject接口和实现

java 复制代码
public interface ISubject {  
    /**  
     * @description 行为方法  
     * @author bigHao  
     * @date 2026/1/17  
     **/    
     void doAction();  
  
    /**  
     * @description 行为方法2  
     * @author bigHao  
     * @date 2026/1/17  
     **/    
     void doAction2();  
}

public class RealSubject implements ISubject {  
    @Override  
    public void doAction() {  
        System.out.println("do action");  
    }  
  
    @Override  
    public void doAction2() {  
        System.out.println("do acton2");  
    }  
}

动态代理

java 复制代码
public class ProxyFactory {  
    private Object target;  
  
    public ProxyFactory(Object target) {  
        this.target = target;  
    }  
  
    public Object getProxyInstance() {  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                System.out.println("proxyName: " + proxy.getClass().getName());  
                System.out.println("methodName: " + method.getName());  
  
                // 代理行为  
                System.out.println("jdk proxy acton start");  
  
                //  反射调用目标对象方法  
                Object invoke = method.invoke(target, args);  
  
                // 代理行为  
                System.out.println("jdk proxy acton end");  
  
                return invoke;  
            }  
        });  
    }  
}

测试:

java 复制代码
public class Client {  
    static void main() {  
        ISubject proxyInstance = (ISubject) new ProxyFactory(new RealSubject()).getProxyInstance();  
  
        System.out.println("=== jdk proxy acton1 ===");  
        proxyInstance.doAction();  
  
        System.out.println("=== jdk proxy acton2 ===");  
        proxyInstance.doAction2();  
    }  
}

输出;

java 复制代码
=== jdk proxy acton1 ===
proxyName: jdk.proxy1.$Proxy0
methodName: doAction
jdk proxy acton start
do action
jdk proxy acton end
=== jdk proxy acton2 ===
proxyName: jdk.proxy1.$Proxy0
methodName: doAction2
jdk proxy acton start
do acton2
jdk proxy acton end

3.3 动态代理-CGLIB代理

3.3.1 实现思路

  1. 无需接口:CGLIB可以代理没有接口的类
  2. 创建MethodInterceptor:实现方法拦截器,定义增强逻辑
  3. 使用Enhancer创建代理:配置Enhancer生成代理类
  4. 生成子类代理:CGLIB通过生成目标类的子类来实现代理
  5. 客户端通过CGLIB代理访问:与JDK代理使用方式类似

3.3.2 UML类图

3.3.3 代码示例

Cglib(Code Generation Library)是个功能强大、高性能、开源的代码生成包,被广泛使用,比如spring等

它可以为没有实现接口 的类提供代理

这里示例是用的spring集成的org.springframework.cglib.proxy。

核心API是包下面的Enhancer、MethodInterceptor,感兴趣可以跟一下源码

需要代理的类(不需要实现接口)

java 复制代码
public class RealSubject {  
    public void doAction() {  
        System.out.println("do action");  
    }  
  
    public void doAction2() {  
        System.out.println("do acton2");  
    }  
}

代理类

java 复制代码
public class ProxyFactory implements MethodInterceptor {  
    private Object target;  
  
    public ProxyFactory(Object target) {  
        this.target = target;  
    }  
  
    public Object getProxyInstance() {  
        // 1.使用api  
        Enhancer enhancer = new Enhancer();  
  
        // 2.设置父类  
        enhancer.setSuperclass(target.getClass());  
  
        // 3.设置回调函数  
        enhancer.setCallback(this);  
  
        // 4.创建代理对象  
        return enhancer.create();  
    }  
  
    @Override  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
        System.out.println("proxyName: " + proxy.getClass().getName());  
        System.out.println("methodName: " + method.getName());  
  
        // 代理行为  
        System.out.println("cglib proxy acton start");  
  
        //  反射调用目标对象方法  
        Object invoke = method.invoke(target, args);  
  
        // 代理行为  
        System.out.println("cglib proxy acton end");  
        return invoke;  
    }  
}

测试

java 复制代码
public class Client {  
    public static void main(String[] args) {  
        RealSubject subject = (RealSubject) new ProxyFactory(new RealSubject()).getProxyInstance();  
  
  
        System.out.println("=== cglib acton1 ===");  
        subject.doAction();  
  
  
        System.out.println("=== cglib acton2 ===");  
        subject.doAction2();  
    }  
}

输出

复制代码
=== cglib acton1 ===
proxyName: org.springframework.cglib.proxy.MethodProxy
methodName: doAction
cglib proxy acton start
do action
cglib proxy acton end
=== cglib acton2 ===
proxyName: org.springframework.cglib.proxy.MethodProxy
methodName: doAction2
cglib proxy acton start
do acton2
cglib proxy acton end

4. 优缺点

4.1 优点

高内聚低耦合

  • 代理对象将附加功能与核心业务逻辑分离
  • 真实对象只需关注核心功能,代理对象处理访问控制、日志等
    提高扩展性
  • 符合开闭原则:可以方便地添加新的代理功能而不修改真实对象
  • 可以动态地添加或移除代理层
    增强安全性
  • 保护代理可以控制对敏感对象的访问权限
  • 可以隐藏真实对象的复杂性和实现细节
    提高性能
  • 虚拟代理实现延迟加载,减少系统启动时间和内存占用
  • 缓存代理避免重复计算,提高响应速度
    提高复用性
  • 代理逻辑可以被多个真实对象复用
  • 动态代理可以通用地处理一类对象的代理需求
    增强可维护性
  • 代理模式将横切关注点(日志、事务、安全)集中管理
  • 便于实现AOP(面向切面编程)

4.2 缺点

增加系统复杂度

  • 引入额外的代理层,增加了系统的复杂性
  • 静态代理需要为每个真实类创建对应的代理类
    可能降低性能
  • 代理调用增加了一层间接层,可能带来轻微的性能开销
  • 动态代理基于反射,性能低于直接调用
    代码可读性降低
  • 调试时堆栈跟踪更复杂,增加了调试难度
  • 代理模式可能使代码流程不够直观
    实现复杂性
  • 动态代理涉及反射和字节码操作,实现较复杂
  • CGLIB代理需要处理final方法、构造函数等特殊情况
    可能的循环调用
  • 如果代理不当,可能导致无限递归调用
  • 需要正确处理代理链的调用顺序

参考:

相关推荐
QiZhang | UESTC2 小时前
学习日记day59
学习
dulu~dulu2 小时前
英语完形填空题型总结
笔记·学习·英语完形填空·自用
微露清风2 小时前
系统性学习C++-第二十四讲-智能指针的使用及其原理
java·c++·学习
Aliex_git2 小时前
GitHub Copilot 使用笔记
笔记·学习·github·copilot·ai编程
BingoXXZ2 小时前
20260114Linux学习笔记
linux·服务器·笔记·学习
小码过河.2 小时前
设计模式——单例模式
单例模式·设计模式
dxnb222 小时前
Datawhale26年1月组队学习:Agentic AI+Task2反思设计模式
学习·设计模式
老蒋每日coding2 小时前
AI智能体设计模式系列(八)—— 记忆管理模式
人工智能·设计模式·golang
好奇龙猫2 小时前
【人工智能学习-AI入试相关题目练习-第五次】
人工智能·学习