当前位置: 首页 > news >正文

代理模式详解:代理、策略与模板方法模式

引言

设计模式是面向对象编程中的经典解决方案,它们封装了前人的经验,提供了可复用的设计思路。本文将重点介绍三种常用的设计模式:代理模式(含静态代理、JDK动态代理、CGLIB代理)、策略模式模板方法模式,并通过代码示例和原理分析帮助读者理解其应用场景和实现方式。

一、代理模式(Proxy Pattern)

1.1 定义与应用场景

代理模式为其他对象提供一种代理以控制对这个对象的访问。核心作用是在不修改目标对象的前提下,通过引入代理对象增强目标对象的功能(如日志记录、事务管理、权限控制等)。

典型应用场景

  • Spring AOP的方法增强
  • RPC框架的服务调用
  • 延迟加载(如Hibernate的懒加载)
  • 权限校验与日志记录

1.2 静态代理

实现原理

静态代理在编译期手动创建代理类,代理类与目标类实现同一接口,并持有目标对象的引用,在调用目标方法前后添加增强逻辑。

代码示例:日志代理
// 1. 定义服务接口
public interface IService {void serve();
}// 2. 实现目标类
public class RealService implements IService {@Overridepublic void serve() {System.out.println("真实服务: 处理核心业务逻辑");}
}// 3. 创建静态代理类
public class StaticProxy implements IService {private final IService target; // 持有目标对象引用public StaticProxy(IService target) {this.target = target;}@Overridepublic void serve() {// 增强逻辑:方法调用前System.out.println("[静态代理] 记录调用开始时间");// 调用目标方法target.serve();// 增强逻辑:方法调用后System.out.println("[静态代理] 记录调用结束时间");}
}// 4. 客户端调用
public class Client {public static void main(String[] args) {IService realService = new RealService();IService proxy = new StaticProxy(realService);proxy.serve();}
}
输出结果:
[静态代理] 记录调用开始时间
真实服务: 处理核心业务逻辑
[静态代理] 记录调用结束时间
优缺点
  • 优点:实现简单,性能较高(编译期确定代理类)。
  • 缺点
    • 代码冗余:每个目标类需对应一个代理类
    • 维护成本高:接口变更时需同步修改代理类

1.3 JDK动态代理

实现原理

JDK动态代理通过反射机制在运行时动态生成代理类,无需手动编写代理类。核心类为java.lang.reflect.ProxyInvocationHandler接口。

  • Proxy类:生成代理对象的工厂类,通过newProxyInstance()方法创建代理实例。
  • InvocationHandler接口:定义代理逻辑的接口,需实现invoke()方法处理增强逻辑。
代码示例:动态日志代理
// 1. 目标接口与实现类(复用静态代理的IService和RealService)// 2. 实现InvocationHandler
public class LogInvocationHandler implements InvocationHandler {private final Object target; // 目标对象public LogInvocationHandler(Object target) {this.target = target;}// 代理逻辑:所有方法调用都会转发到invoke()@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[JDK动态代理] 方法" + method.getName() + "调用开始");Object result = method.invoke(target, args); // 反射调用目标方法System.out.println("[JDK动态代理] 方法" + method.getName() + "调用结束");return result;}// 创建代理对象public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(),   // 目标类实现的接口this                                // InvocationHandler实例);}
}// 3. 客户端调用
public class Client {public static void main(String[] args) {IService realService = new RealService();IService proxy = (IService) new LogInvocationHandler(realService).getProxy();proxy.serve();}
}
底层原理分析
  1. 代理类生成Proxy.newProxyInstance()通过ProxyGenerator生成代理类字节码,类名格式为com.sun.proxy.$ProxyN
  2. 方法转发:生成的代理类实现目标接口,并重写方法,将调用转发到InvocationHandler.invoke()
  3. 反射调用invoke()方法通过Method.invoke()反射调用目标方法,实现增强逻辑与业务逻辑的解耦。
局限性
  • 必须实现接口:JDK动态代理只能代理实现了接口的类。
  • 性能开销:反射调用比直接调用慢,适合代理逻辑复杂但调用频率低的场景。

1.4 CGLIB动态代理

实现原理

CGLIB(Code Generation Library)通过字节码生成技术动态创建目标类的子类,并重写非final方法实现代理。核心类为EnhancerMethodInterceptor接口。

  • Enhancer:CGLIB的核心类,用于创建代理对象。
  • MethodInterceptor:方法拦截器接口,需实现intercept()方法定义增强逻辑。
代码示例:CGLIB代理
// 1. 引入CGLIB依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>// 2. 目标类(无需实现接口)
public class UserService {public void createUser(String name) {System.out.println("创建用户: " + name);}
}// 3. 实现MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("[CGLIB代理] 方法" + method.getName() + "调用开始");Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法System.out.println("[CGLIB代理] 方法" + method.getName() + "调用结束");return result;}
}// 4. 创建代理对象
public class Client {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class); // 设置父类(目标类)enhancer.setCallback(new LogMethodInterceptor()); // 设置拦截器UserService proxy = (UserService) enhancer.create(); // 创建代理对象proxy.createUser("张三");}
}
底层原理分析
  1. 子类生成:CGLIB使用ASM库生成目标类的子类,类名格式为目标类名$$EnhancerByCGLIB$$随机字符串
  2. 方法重写:子类重写目标类的非final方法,在方法中调用MethodInterceptor.intercept()
  3. 字节码操作:直接修改字节码,比JDK动态代理性能更高(避免反射调用)。
局限性
  • 无法代理final类/方法:由于基于继承,final类或方法无法被重写。
  • 安全性风险:字节码操作可能绕过权限检查,需谨慎使用。

1.5 三种代理方式对比

特性静态代理JDK动态代理CGLIB代理
实现方式手动编写代理类反射+接口ASM字节码+继承
代理目标接口或类仅接口类(非final)
性能高(直接调用)中(反射调用)高(字节码生成)
灵活性低(编译期确定)中(运行时生成)高(运行时生成)
典型应用简单增强场景Spring AOP(接口代理)Spring AOP(类代理)

二、策略模式(Strategy Pattern)

2.1 定义与应用场景

策略模式定义一系列算法,将每个算法封装为独立的策略类,并使它们可相互替换。核心作用是消除复杂的条件判断(if-else/switch),实现算法的动态切换。
在这里插入图片描述

典型应用场景

  • 支付方式选择(支付宝、微信、银行卡)
  • 排序算法切换(快速排序、冒泡排序)
  • 折扣策略(满减、打折、优惠券)

2.2 结构与实现

核心角色
  • 策略接口(Strategy):定义算法的公共接口。
  • 具体策略(Concrete Strategy):实现策略接口的具体算法。
  • 上下文(Context):持有策略对象的引用,负责调用策略。
代码示例:支付策略
// 1. 策略接口
public interface PaymentStrategy {void pay(double amount);
}// 2. 具体策略:支付宝支付
public class AlipayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付: " + amount + "元");}
}// 3. 具体策略:微信支付
public class WechatPayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付: " + amount + "元");}
}// 4. 上下文类
public class PaymentContext {private PaymentStrategy strategy;// 构造器注入策略public PaymentContext(PaymentStrategy strategy) {this.strategy = strategy;}// 动态切换策略public void setStrategy(PaymentStrategy strategy) {this.strategy = strategy;}// 执行支付public void executePayment(double amount) {strategy.pay(amount);}
}// 5. 客户端调用
public class Client {public static void main(String[] args) {PaymentContext context = new PaymentContext(new AlipayStrategy());context.executePayment(100); // 使用支付宝支付context.setStrategy(new WechatPayStrategy());context.executePayment(200); // 切换为微信支付}
}
输出结果:
使用支付宝支付: 100.0元
使用微信支付: 200.0元

2.3 优缺点

  • 优点

    • 符合开闭原则:新增策略无需修改原有代码。
    • 消除条件语句:用多态替代if-else判断。
    • 算法复用:策略类可在不同场景复用。
  • 缺点

    • 策略类数量增多:每个策略对应一个类。
    • 客户端需了解策略:客户端需知道所有策略并选择合适的策略。

三、模板方法模式(Template Method Pattern)

3.1 定义与应用场景

模板方法模式定义算法的骨架,将某些步骤延迟到子类实现。核心作用是复用公共流程,差异化实现细节,确保算法结构的稳定性。

典型应用场景

  • 框架设计(如Spring的JdbcTemplate)
  • 固定流程的业务逻辑(如报表生成、数据导入)
  • 生命周期管理(如Servlet的init-service-destroy)

3.2 结构与实现

核心角色
  • 抽象类(Abstract Class):定义模板方法(算法骨架)和基本方法(抽象方法、具体方法、钩子方法)。
  • 具体子类(Concrete Class):实现抽象方法,覆盖钩子方法(可选)。
代码示例:饮料制作流程
// 1. 抽象类(模板)
public abstract class Beverage {// 模板方法:定义算法骨架(final防止子类修改流程)public final void prepareRecipe() {boilWater();       // 公共步骤brew();            // 抽象步骤(子类实现)pourInCup();       // 公共步骤if (customerWantsCondiments()) { // 钩子方法控制流程addCondiments(); // 抽象步骤(子类实现)}}// 抽象方法:冲泡(子类实现)protected abstract void brew();// 抽象方法:加调料(子类实现)protected abstract void addCondiments();// 具体方法:烧水(公共步骤)private void boilWater() {System.out.println("烧水");}// 具体方法:倒入杯子(公共步骤)private void pourInCup() {System.out.println("倒入杯子");}// 钩子方法:是否加调料(默认加)protected boolean customerWantsCondiments() {return true;}
}// 2. 具体子类:咖啡
public class Coffee extends Beverage {@Overrideprotected void brew() {System.out.println("冲泡咖啡粉");}@Overrideprotected void addCondiments() {System.out.println("加糖和牛奶");}// 覆盖钩子方法:询问用户是否加调料@Overrideprotected boolean customerWantsCondiments() {return askUser(); // 模拟用户输入}private boolean askUser() {System.out.println("是否加糖和牛奶?(y/n)");return true; // 简化示例,默认返回true}
}// 3. 具体子类:茶
public class Tea extends Beverage {@Overrideprotected void brew() {System.out.println("浸泡茶叶");}@Overrideprotected void addCondiments() {System.out.println("加柠檬");}
}// 4. 客户端调用
public class Client {public static void main(String[] args) {Beverage coffee = new Coffee();coffee.prepareRecipe();// 输出:烧水 → 冲泡咖啡粉 → 倒入杯子 → 是否加糖和牛奶? → 加糖和牛奶Beverage tea = new Tea();tea.prepareRecipe();// 输出:烧水 → 浸泡茶叶 → 倒入杯子 → 加柠檬}
}

3.3 钩子方法的作用

钩子方法(Hook Method)是模板方法模式的关键扩展点,用于:

  • 控制流程:如示例中customerWantsCondiments()决定是否加调料。
  • 提供默认实现:子类可选择是否覆盖。
  • 扩展功能:在不修改模板方法的前提下增加新逻辑。

3.4 优缺点

  • 优点

    • 代码复用:公共流程在父类中实现,子类共享。
    • 强制流程:子类无法修改算法骨架,确保一致性。
    • 扩展性好:子类通过实现抽象方法扩展功能。
  • 缺点

    • 增加抽象类:系统复杂度提高。
    • 子类依赖父类:父类修改可能影响所有子类。

四、总结

三种模式的对比与选择

模式核心思想典型应用场景关键角色/类
代理模式控制对象访问,增强功能AOP、权限控制、延迟加载Proxy、InvocationHandler
策略模式封装算法,动态切换支付方式、排序算法、折扣策略Strategy接口、Context
模板方法固定流程,延迟步骤实现框架设计、固定流程业务逻辑抽象类(模板方法+钩子方法)

实践建议

  1. 代理模式:优先使用JDK动态代理(接口代理),无接口时选择CGLIB。
  2. 策略模式:当存在3个以上可替换算法时使用,结合工厂模式管理策略。
  3. 模板方法:流程固定但步骤实现可变时使用,通过钩子方法提供灵活性。

设计模式的核心价值在于解耦与复用,实际开发中需结合业务场景灵活选择,避免过度设计。

http://www.lryc.cn/news/588480.html

相关文章:

  • 暑期自学嵌入式——Day02(C语言阶段)
  • PyTorch张量(Tensor)创建的方式汇总详解和代码示例
  • 如何降低AIGC的查重率?精选六个AIGC降重让论文更出色
  • 《每日AI-人工智能-编程日报》--2025年7月14日
  • Android Studio C++/JNI/Kotlin 示例 三
  • git项目,有idea文件夹,怎么去掉
  • Mybatis(黑马)
  • 网络传输过程
  • 理解Linux文件系统:从物理存储到统一接口
  • 小波变换 | 离散小波变换
  • 学习笔记——农作物遥感识别与大范围农作物类别制图的若干关键问题
  • rsyslog简单应用
  • Linux中的系统日志(Rsyslog)
  • 算法训练营day17 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树
  • Linux —— A / 基础指令
  • 深入解析Hadoop YARN架构设计:从原理到实践
  • 019 进程控制 —— 进程程序替换
  • SpringMVC2
  • 力扣-138.随机链表的复制
  • 一分钟K线实时数据数据接口,逐笔明细数据接口,分时成交量数据接口,实时五档委托单数据接口,历史逐笔明细数据接口,历史分时成交量数据接口
  • 深入理解MyBatis延迟加载:原理、配置与实战优化
  • 美丽田园发布盈喜公告,预计净利增长超35%该咋看?
  • 现场设备无法向视频汇聚EasyCVR视频融合平台推流的原因排查与解决过程
  • CA-IS3082W 隔离485 收发器芯片可能存在硬件BUG
  • 第十五节:Vben Admin 最新 v5.0 (vben5) + Python Flask 快速入门 - vue前端 生产部署
  • Laravel 中 chunk 分页漏掉数据?深度解析原因与解决方案
  • Unity3D + VS2022连接雷电模拟器调试
  • 4、qt窗口(沉淀中)
  • iOS APP 上架流程:跨平台上架方案的协作实践记录
  • ConcurrentHashMap 原子操作详解:computeIfAbsent、computeIfPresent和putIfAbsent