代理模式详解:代理、策略与模板方法模式
引言
设计模式是面向对象编程中的经典解决方案,它们封装了前人的经验,提供了可复用的设计思路。本文将重点介绍三种常用的设计模式:代理模式(含静态代理、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.Proxy
和InvocationHandler
接口。
- 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();}
}
底层原理分析
- 代理类生成:
Proxy.newProxyInstance()
通过ProxyGenerator
生成代理类字节码,类名格式为com.sun.proxy.$ProxyN
。 - 方法转发:生成的代理类实现目标接口,并重写方法,将调用转发到
InvocationHandler.invoke()
。 - 反射调用:
invoke()
方法通过Method.invoke()
反射调用目标方法,实现增强逻辑与业务逻辑的解耦。
局限性
- 必须实现接口:JDK动态代理只能代理实现了接口的类。
- 性能开销:反射调用比直接调用慢,适合代理逻辑复杂但调用频率低的场景。
1.4 CGLIB动态代理
实现原理
CGLIB(Code Generation Library)通过字节码生成技术动态创建目标类的子类,并重写非final方法实现代理。核心类为Enhancer
和MethodInterceptor
接口。
- 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("张三");}
}
底层原理分析
- 子类生成:CGLIB使用ASM库生成目标类的子类,类名格式为
目标类名$$EnhancerByCGLIB$$随机字符串
。 - 方法重写:子类重写目标类的非final方法,在方法中调用
MethodInterceptor.intercept()
。 - 字节码操作:直接修改字节码,比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 |
模板方法 | 固定流程,延迟步骤实现 | 框架设计、固定流程业务逻辑 | 抽象类(模板方法+钩子方法) |
实践建议
- 代理模式:优先使用JDK动态代理(接口代理),无接口时选择CGLIB。
- 策略模式:当存在3个以上可替换算法时使用,结合工厂模式管理策略。
- 模板方法:流程固定但步骤实现可变时使用,通过钩子方法提供灵活性。
设计模式的核心价值在于解耦与复用,实际开发中需结合业务场景灵活选择,避免过度设计。