代理模式(Proxy Pattern)
代理模式(Proxy Pattern)是一种结构型设计模式,允许你通过代理对象来控制对其他对象的访问。代理模式的主要目的是通过代理对象来控制原对象的访问、延迟加载、权限控制等。
组成结构
- Subject(主题接口):定义了真实对象和代理对象的共同接口。
- RealSubject(真实主题类):定义了代理类所代表的真实对象,通常实现了Subject接口。
- Proxy(代理类):持有RealSubject的引用,并在请求传递给RealSubject之前或之后进行一些操作。
代理模式的分类
- 静态代理:代理类在编译时就已经确定。代理类和被代理类通常是手动创建的,代码需要在编译时就确定好。
- 动态代理:代理类在运行时动态生成,通常借助反射机制来生成代理对象。这种方式更灵活,可以通过代理类来代理多个不同的接口或类。
- 虚拟代理:通过代理类控制对某个对象的访问,常用于懒加载,只有在实际需要时才初始化对象,减少资源消耗。
- 保护代理:控制对对象的访问权限,通常用于权限控制等。
- 远程代理:用于对象在不同地址空间的访问,例如,分布式系统中,客户端通过代理访问远程对象。
由于篇幅原因,远程代理会放在另一篇文章单独举例。
根据代理模式的分类,以下是各类型代理的具体案例和应用:
1. 静态代理(Static Proxy)
静态代理是在编译时确定的,代理类和真实类都需要在编写时定义。代理类通常会实现与真实类相同的接口,并在代理类中调用真实类的方法。一个具体类对应一个代理类,通过代理类来操作具体类。
案例:静态代理的日志记录
假设我们有一个计算服务,通过代理类来记录方法执行的日志。- // Subject接口
- public interface CalculationService {
- int add(int a, int b);
- int subtract(int a, int b);
- }
- // 真实类
- public class CalculationServiceImpl implements CalculationService {
- @Override
- public int add(int a, int b) {
- return a + b;
- }
- @Override
- public int subtract(int a, int b) {
- return a - b;
- }
- }
- // 代理类
- public class CalculationServiceProxy implements CalculationService {
- private CalculationServiceImpl realService;
- public CalculationServiceProxy() {
- realService = new CalculationServiceImpl();
- }
- @Override
- public int add(int a, int b) {
- System.out.println("日志: add ...");
- return realService.add(a, b);
- }
- @Override
- public int subtract(int a, int b) {
- System.out.println("日志: subtract ...");
- return realService.subtract(a, b);
- }
- }
- // 客户端
- public class Client {
- public static void main(String[] args) {
- CalculationService service = new CalculationServiceProxy();
- System.out.println("执行结果: " + service.add(10, 5));
- System.out.println("执行结果: " + service.subtract(10, 5));
- }
- }
复制代码 执行结果:
日志: add ...
执行结果: 15
日志: subtract ...
执行结果: 5
2. 动态代理(Dynamic Proxy)
动态代理是在运行时动态生成的,可以使用 JDK 动态代理(Proxy.newProxyInstance())或CGLib动态代理创建一个代理对象,并在其中增强目标对象的功能。多个具体类使用一个代理类来创建代理对象。
案例:动态代理的日志记录
通过java.lang.reflect.Proxy和InvocationHandler来动态创建代理类,实现类似静态代理的功能。动态代理不需要像静态代理那样在每个业务代码方法都加入日志代码,而是统一的日志记录,并且与业务代码解耦,起到低耦合的作用,日志代码(代理类)的调整不会影响到业务代码。
案例类图
使用JDK动态代理实现
- import java.lang.reflect.*;
- public interface CalculationService {
- int add(int a, int b);
- int subtract(int a, int b);
- }
- public class CalculationServiceImpl implements CalculationService {
- @Override
- public int add(int a, int b) {
- return a + b;
- }
- @Override
- public int subtract(int a, int b) {
- return a - b;
- }
- }
- public class LoggingInvocationHandler implements InvocationHandler {
- private Class<?> targetClass; // 目标类的 Class 对象
- public LoggingInvocationHandler(Class<?> targetClass) {
- this.targetClass = targetClass;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 在方法调用前打印日志
- System.out.println("日志: 执行方法 " + method.getName() + " 参数为 " + args[0] + " 和 " + args[1]);
- // 使用反射创建目标对象实例
- Object target = targetClass.getDeclaredConstructor().newInstance();
- // 使用反射调用目标对象的方法
- return method.invoke(target, args); // 调用目标类的方法并传递参数
- }
- }
- public class Client {
- public static void main(String[] args) {
- try {
- // 使用反射创建代理对象
- Class<?> targetClass = CalculationServiceImpl.class; // 获取目标类的 Class 对象
- // 创建动态代理
- CalculationService proxy = (CalculationService) Proxy.newProxyInstance(
- targetClass.getClassLoader(), // 类加载器
- targetClass.getInterfaces(), // 接口列表
- new LoggingInvocationHandler(targetClass) // 传入目标类的 Class 对象
- );
- // 通过代理对象调用方法
- System.out.println("执行结果: " + proxy.add(10, 5));
- System.out.println("执行结果: " + proxy.subtract(10, 5));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
复制代码 执行结果
日志: 执行方法 add 参数为 10 和 5
执行结果: 15
日志: 执行方法 subtract 参数为 10 和 5
执行结果: 5
使用CGLib动态代理实现
- <dependency>
- <groupId>cglib</groupId>
- cglib</artifactId>
- <version>3.3.0</version>
- </dependency>
复制代码 创建 CGLIB 代理类
- public class CGLibProxyExample {
- // 使用 CGLIB 动态代理
- public static void main(String[] args) {
- // 创建目标对象
- CalculationServiceImpl realService = new CalculationServiceImpl();
- // 创建 CGLIB 代理对象
- CalculationServiceImpl proxyService = (CalculationServiceImpl) Enhancer.create(
- CalculationServiceImpl.class, // 目标类
- new MethodInterceptor() { // 方法拦截器
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- // 方法调用前打印日志
- System.out.println("日志: 执行方法 " + method.getName() + " 参数为 " + args[0] + " 和 " + args[1]);
- // 调用目标方法
- return proxy.invokeSuper(obj, args); // 调用父类(目标类)的方法
- }
- }
- );
- // 通过代理对象调用方法
- System.out.println("执行结果: " + proxyService.add(10, 5));
- System.out.println("执行结果: " + proxyService.subtract(10, 5));
- }
- }
复制代码 3. 虚拟代理(Virtual Proxy)
虚拟代理通常用于对象的懒加载(Lazy Initialization)。当某个对象的创建非常昂贵(如需要加载大量数据或资源),而并非每次都需要使用时,虚拟代理可以推迟对象的创建,直到需要时才创建。
案例:虚拟代理的懒加载
模拟一个大型图片的加载,只有在需要显示图片时,才加载图片数据。- // Subject接口
- public interface Image {
- void display();
- }
- // 真实对象
- public class RealImage implements Image {
- private String fileName;
- public RealImage(String fileName) {
- this.fileName = fileName;
- loadFromDisk();
- }
- private void loadFromDisk() {
- System.out.println("Loading image: " + fileName);
- }
- @Override
- public void display() {
- System.out.println("Displaying image: " + fileName);
- }
- }
- // 代理类
- public class ProxyImage implements Image {
- private RealImage realImage;
- private String fileName;
- public ProxyImage(String fileName) {
- this.fileName = fileName;
- }
- @Override
- public void display() {
- if (realImage == null) {
- realImage = new RealImage(fileName);
- }
- realImage.display();
- }
- }
- // 客户端
- public class Client {
- public static void main(String[] args) {
- Image image = new ProxyImage("test.jpg");
- image.display(); // 第一次调用时加载图片
- image.display(); // 第二次调用时直接显示
- }
- }
复制代码 执行结果:
Loading image: test.jpg
Displaying image: test.jpg
Displaying image: test.jpg
4. 保护代理(Protective Proxy)
保护代理控制访问权限,通常用于安全控制。例如,代理对象可以检查用户权限,只有在满足条件时才允许访问真实对象。
案例:保护代理
假设我们有一个银行账户服务,保护代理会在执行操作之前验证用户是否有足够的权限。- // 账户服务接口
- public interface BankAccountService {
- void withdraw(int amount);
- void deposit(int amount);
- }
- // 真实对象
- public class BankAccountServiceImpl implements BankAccountService {
- private int balance = 1000;
- @Override
- public void withdraw(int amount) {
- if (balance >= amount) {
- balance -= amount;
- System.out.println("转出: " + amount + ", 剩余余额: " + balance);
- } else {
- System.out.println("资金不足!");
- }
- }
- @Override
- public void deposit(int amount) {
- balance += amount;
- System.out.println("转入: " + amount + ", 剩余余额: " + balance);
- }
- }
- // 保护代理
- public class BankAccountServiceProxy implements BankAccountService {
- private BankAccountServiceImpl realService;
- private String userRole;
- public BankAccountServiceProxy(String userRole) {
- realService = new BankAccountServiceImpl();
- this.userRole = userRole;
- }
- @Override
- public void withdraw(int amount) {
- if ("admin".equals(userRole)) {
- realService.withdraw(amount);
- } else {
- System.out.println("访问被拒绝:权限不足!");
- }
- }
- @Override
- public void deposit(int amount) {
- realService.deposit(amount);
- }
- }
- // 客户端
- public class Client {
- public static void main(String[] args) {
- BankAccountService userService = new BankAccountServiceProxy("user");
- userService.withdraw(200); // Should be denied
- BankAccountService adminService = new BankAccountServiceProxy("admin");
- adminService.withdraw(200); // Should be allowed
- }
- }
复制代码 执行结果
访问被拒绝:权限不足!
转出: 200, 剩余余额: 800
优缺点和应用场景
优点:
- 提供了对真实对象的控制,可以增加额外的功能,比如访问控制、缓存、延迟加载、日志记录等等。
- 通过动态代理可以减少代码冗余,提高代码的灵活性和可扩展性。
缺点:
- 增加了系统的复杂度,因为每个真实对象都需要有一个代理类来配合工作。
- 如果代理层次过多,可能会影响性能,尤其是动态代理在性能上会有一定的损耗。
应用场景:
- 延迟加载:当一个对象的创建成本非常高,且不一定每次都需要使用该对象时,可以通过代理来控制对象的创建时机,避免不必要的资源浪费。
- 访问控制:通过代理类可以控制对原对象的访问权限,只有在满足特定条件下才允许访问。
- 远程代理:当对象在远程服务器上时,可以通过代理类来模拟远程对象的行为。
- 日志记录和性能监控:在代理类中可以加入日志记录、性能监控等功能,不需要修改真实对象的代码。
总结
这些不同类型的代理模式根据应用场景和需求各自发挥作用,以上只是常见的几种情况。
比如:Spring AOP使用了动态代理模式+自定义注解实现的切面编程;JavaRMI Java平台之间的远程方法调用 ;RPC框架实现跨平台的远程过程调用。
代理模式的思想不仅仅是代码的编写,比如服务器的正向代理和反向代理。代理对象同样可以起到服务访问日志记录(审计);请求服务器只知道代理服务器的IP,对于目标服务器起到安全保护,同时可以设置IP的黑白名单和限流等;服务器网络之间的解耦,比如目标服务器IP地址发生变化时只需要调整代理服务器的配置即可,而无需调整客户端请求的目标地址。
掌握设计模式及其编程思想才能做到以不变应万变,真正做到学以致用,举一反三的效果。
需要查看往期设计模式文章的,可以在个人主页中或者文章开头的集合中查看,可关注我,持续更新中。。。
超实用的SpringAOP实战之日志记录
2023年下半年软考考试重磅消息
通过软考后却领取不到实体证书?
计算机算法设计与分析(第5版)
Java全栈学习路线、学习资源和面试题一条龙
软考证书=职称证书?
软考中级--软件设计师毫无保留的备考分享
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |