结构型模式-3.代理模式(Proxy Pattern)

 2024-07-29    0 条评论    66 浏览

设计模式

代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它允许你提供一个代理对象,控制对其他对象的访问。

代理模式的角色

  1. Subject(抽象主题):定义了RealSubject和Proxy的共用接口,客户端通过该接口访问RealSubject。
  2. RealSubject(真实主题):定义了Proxy所代表的真实对象。
  3. Proxy(代理):保存一个引用使得代理可以访问Subject,并提供一个与Subject接口相同的接口,这样代理就可以用来代替实体对象。

代理模式的优点

  • 远程代理(Remote Proxy):可以在不同的地址空间中代表一个对象,例如在网络上的另一台服务器上。
  • 虚拟代理(Virtual Proxy):根据需要创建开销较大的对象。例如,一个图片查看器可以用虚拟代理来延迟实际加载图片直到用户查看了这个图片。
  • 保护代理(Protection Proxy):控制对原始对象的访问。例如,只有特定用户才能访问。

Java 代码示例

让我们看一个简单的示例,假设我们有一个文件服务接口 FileService,它有一个方法 downloadFile() 用于下载文件。我们将实现一个代理 FileServiceProxy 来控制对文件的访问。

1. 定义接口 FileService

// Subject
public interface FileService {
    void downloadFile(String fileName);
}

2. 实现真实主题 FileServiceImpl

// RealSubject
public class FileServiceImpl implements FileService {
    private String fileName;

    public FileServiceImpl(String fileName) {
        this.fileName = fileName;
        loadFileFromDisk(); // Simulating heavy operation
    }

    private void loadFileFromDisk() {
        System.out.println("Loading file: " + fileName);
    }

    @Override
    public void downloadFile(String fileName) {
        System.out.println("Downloading file: " + fileName);
    }
}

3. 实现代理 FileServiceProxy

// Proxy
public class FileServiceProxy implements FileService {
    private FileService fileService;

    public FileServiceProxy(String fileName) {
        this.fileService = new FileServiceImpl(fileName);
    }

    @Override
    public void downloadFile(String fileName) {
        fileService.downloadFile(fileName);
    }
}

4. 使用代理模式:

public class Main {
    public static void main(String[] args) {
        FileService fileService = new FileServiceProxy("example.txt");
        fileService.downloadFile("example.txt");
    }
}

在这个示例中,FileServiceImpl 是真实主题,负责实际的文件操作。FileServiceProxy 是代理,控制对 FileServiceImpl 的访问,并且在需要时创建真实主题的实例。

动态代理

JDK动态代理

在Java中,动态代理是一种在运行时创建代理类的机制。动态代理通常用于实现横切关注点(如日志记录、安全性、事务管理等),使得代码更具可复用性和可维护性。Java提供了java.lang.reflect.Proxy类和InvocationHandler接口来支持动态代理的实现。

1. 动态代理的基本概念

  • 代理:代理是一个代表另一个对象的对象。代理对象通常会在调用目标对象的方法时,执行一些额外的操作。
  • 目标对象:被代理的实际对象。
  • 代理类:代理对象的类,这个类在运行时动态生成。

2. 动态代理的工作原理

动态代理的工作流程如下:

  1. 定义一个接口(目标接口),被代理的类实现该接口。
  2. 创建一个实现了InvocationHandler接口的类,重写invoke方法,在该方法中定义代理逻辑。
  3. 使用Proxy.newProxyInstance方法创建代理实例。

3. 代码示例

以下是一个简单的动态代理示例,展示了如何使用动态代理来拦截方法调用:

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

// 目标接口
interface HelloService {
    void sayHello(String name);
}

// 目标类
class HelloServiceImpl implements HelloService {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

// InvocationHandler实现
class DynamicProxyHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法前执行的逻辑
        System.out.println("Before method: " + method.getName());
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        // 在调用目标方法后执行的逻辑
        System.out.println("After method: " + method.getName());
        
        return result;
    }
}

// 测试动态代理
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        HelloService helloService = new HelloServiceImpl();
        
        // 创建动态代理
        HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
            helloService.getClass().getClassLoader(),
            helloService.getClass().getInterfaces(),
            new DynamicProxyHandler(helloService));
        
        // 调用代理对象的方法
        proxyInstance.sayHello("World");
    }
}

4. 运行结果

当运行上述代码时,输出将如下:

Before method: sayHello
Hello, World
After method: sayHello

5. 动态代理的优势

  • 灵活性:动态代理允许在运行时决定如何处理方法调用,提供了高度的灵活性。
  • 减少代码重复:可以将通用的逻辑(如日志、权限检查)集中到代理类中,避免在每个实现类中重复编写相同的代码。
  • 解耦:使用接口和代理,目标对象与代理之间解耦,便于替换和扩展。

6. 注意事项

  • 动态代理只能对实现了接口的对象进行代理。如果需要对没有实现接口的类进行代理,可以考虑使用CGLIB等第三方库。
  • 代理类的性能可能相对静态代理稍低,因为动态代理在运行时生成代理类和进行方法调用。

结论

动态代理是Java中一个强大的特性,可以帮助开发者在不修改目标对象的情况下增强其功能。通过灵活地使用动态代理,可以提高代码的可维护性和可复用性。

cglib动态代理

cglib动态代理是一个基于字节码操作的框架,它允许在运行时为目标类生成一个代理类。这个代理类会拦截目标类的所有方法调用,并在调用前后执行一些自定义逻辑。

cglib动态代理的主要特性:

  • 生成代理类: cglib动态代理能够根据目标类的字节码生成一个继承目标类的代理类。 这使得代理类能够访问和调用目标类的所有成员方法。
  • 拦截方法调用: 代理类在目标类方法被调用之前或之后可以拦截并修改方法的参数和返回值。
  • 代码执行: cglib动态代理可以在代理类中执行任何自定义代码,例如日志记录、权限验证和事务管理。
  • 支持接口: cglib 支持代理实现接口,并通过接口的实现来拦截方法调用。

cglib 动态代理与JDK 动态代理的比较:

特性cglib 动态代理JDK 动态代理
基于字节码操作接口反射
代理类型生成代理类创建代理实例
支持任何类接口
效率相对较高相对较低
区别继承代理类实现相同接口

cglib 动态代理的应用场景:

  • AOP (面向切面编程): cglib 可以用于实现 AOP 的功能,例如日志记录、事务管理和权限验证。
  • 数据缓存: cglib 可以用于在方法调用之前先检查缓存,避免重复计算。
  • 性能监控: cglib 可以用于监控方法的执行时间和消耗的资源。

二者选择

在Spring框架中,选择使用cglib还是JDK代理通常取决于被代理的类是否实现了接口。

  1. JDK动态代理

    • JDK代理基于接口,它要求被代理的类至少实现一个接口。
    • 当目标对象实现了接口时,Spring会选择使用JDK动态代理。
    • JDK代理是通过Java反射机制来实现的,可以在运行时动态创建代理类。
  2. CGLIB代理

    • 如果目标对象没有实现接口,Spring就会使用CGLIB代理。
    • CGLIB(Code Generation Library)是一个强大的,高性能的代码生成库,它可以在运行时扩展Java类与实现接口。
    • CGLIB通过继承被代理类来实现代理。

如何选择:

  • 性能:通常情况下,CGLIB比JDK动态代理更快,因为它是通过生成字节码来动态创建子类。
  • 接口依赖:如果你的代码要求基于接口的代理,你只能使用JDK代理。
  • 类层次结构:如果你需要代理一个类,而不是一个接口,并且你不希望修改它的源码,那么CGLIB是更好的选择。

总结来说,如果你的类实现了接口,你可以选择使用JDK代理。如果你要代理的类没有实现接口,或者你希望强制使用类代理,那么使用CGLIB可能更适合。在Spring中,通常这些选择是自动处理的,取决于被代理的类的类型和配置。