返回 导航

其他

hangge.com

Java - 静态代理与动态代理详解(附:Cglib的使用)

作者:hangge | 2023-01-11 08:40
    代理模式(Proxy)是为目标对象提供一种代理,从而能够在不改变目标对象的情况下,对目标对象的访问进行控制。例如:访问权限的控制、访问地址的控制、访问方式的控制等。而根据代理类的创建时间又可以分为静态代理和动态代理。下面通过样例分别演示 Java 中静态代理与动态代理如何实现。

一、静态代理

1,基本介绍

(1)静态代理指在程序运行前就已经存在代理类的字节码文件,代理类和被目标类的关系在运行前就确定了。
(2)由于静态代理需要手动编写每一个代理类,因此具有如下不足:
  • 代理类的数量是固定的
  • 代码复杂度较高
  • 不能很好地扩展

2,样例代码

(1)下面通过样例演示如何使用静态代理模式实现安全访问控制。首先定义一个文件操作接口 FileService,声明需要实现的文件操作方法:
// 文件操作接口
public interface FileService {
  void readFile(String fileName);
  void writeFile(String fileName, String content);
}

(2)接着定义一个文件操作类 RealFileService 实现这些操作方法:
// 文件操作类
public class RealFileService implements FileService {
  @Override
  public void readFile(String fileName) {
    // 实际的读取文件操作
    System.out.println("读取" + fileName + "文件内容");
  }

  @Override
  public void writeFile(String fileName, String content) {
    // 实际的写入文件操作
    System.out.println("将\"" + content + "\"写入到" + fileName + "文件中");
  }
}

(3)然后定义一个代理类 FileServiceSecurityProxy,它充当了客户端和 RealFileService 类之间的中介。FileServiceSecurityProxy 会在调用 RealFileService 类的方法之前检查当前用户是否有权限访问文件服务。如果没有权限,就会抛出 SecurityException 异常。这样就能实现安全访问控制的目的。
//文件操作安全代理类
public class FileServiceSecurityProxy implements FileService{

  // 文件服务对象引用
  private FileService fileService;

  // 用户许可名单
  private Set<String> authorizedUsers;

  public FileServiceSecurityProxy(Set<String> authorizedUsers) {
    this.authorizedUsers = authorizedUsers;
  }

  @Override
  public void readFile(String fileName) {
    System.out.println("--- 准备调用方法:readFile ---");
    // 获取当前系统的用户名
    String currentUser = System.getProperty("user.name");
    // 判断用户是否存在许可名单中
    if (!authorizedUsers.contains(currentUser)) {
      throw new SecurityException("用户 " + currentUser + " 没有权限使用文件服务。");
    }
    // 延迟加载 RealFileService 对象
    if (fileService == null) {
      fileService = new RealFileService();
    }
    // 使用 RealFileService 对象读文件
    fileService.readFile(fileName);
    System.out.println("--- 方法调用结束:readFile ---");
  }

  @Override
  public void writeFile(String fileName, String content) {
    System.out.println("--- 准备调用方法:writeFile ---");
    // 获取当前系统的用户名
    String currentUser = System.getProperty("user.name");
    // 判断用户是否存在许可名单中
    if (!authorizedUsers.contains(currentUser)) {
      throw new SecurityException("用户 " + currentUser + " 没有权限使用文件服务。");
    }
    // 延迟加载 RealFileService 对象
    if (fileService == null) {
      fileService = new RealFileService();
    }
    // 使用 RealFileService 对象写文件
    fileService.writeFile(fileName, content);
    System.out.println("--- 方法调用结束:writeFile ---");
  }
}

(4)最后测试一下:
public class Test {
  @SneakyThrows
  public static void main(String[] args) {
    // 初始化许可用户列表
    Set<String> authorizedUsers = new HashSet<>(Arrays.asList("hangge", "user2"));
    // 初始化代理对象
    FileServiceSecurityProxy proxy = new FileServiceSecurityProxy(authorizedUsers);
    // 调用代理对象的读文件方法
    proxy.readFile("file.txt");
    // 调用代理对象的写文件方法
    proxy.writeFile("file.txt", "content");
  }
}

(5)假设当前系统用户存在于许可列表中,运行结果如下:

(6)如果当前系统用户不属于许可名单,则运行结果如下:

二、动态代理

1,基本介绍

(1)动态代理的代理类在程序运行期间优 JVM 根据反射等机制动态生成,在程序运行前并不存在代理类的字节码文件。
(2)由于动态代理可以通过反射机制动态生成代理类,因此具有如下优点:
  • 可以动态生成任意数量的代理类
  • 代码复杂度较低
  • 可以很好地扩展
(3)JDK1.3 版本就开始支持动态代理类的创建。java.lang.reflect 包中的 Proxy 类提供了用于创建动态代理类和实例的方法。使用动态代理需要提供一个接口和一个 InvocationHandler 实例(InvocationHandler 接口也在 java.lang.reflect 包中),Proxy 类会根据这些信息生成动态代理类。

2,样例代码

(1)我们同样以安全访问控制为例演示动态代理的使用。首先和上面一样,定义一个文件操作接口 FileService,声明需要实现的文件操作方法:
// 文件操作接口
public interface FileService {
  void readFile(String fileName);
  void writeFile(String fileName, String content);
}

(2)接着定义一个文件操作类 RealFileService 实现这些操作方法,这个还是和上面一样:
// 文件操作类
public class RealFileService implements FileService {
  @Override
  public void readFile(String fileName) {
    // 实际的读取文件操作
    System.out.println("读取" + fileName + "文件内容");
  }

  @Override
  public void writeFile(String fileName, String content) {
    // 实际的写入文件操作
    System.out.println("将\"" + content + "\"写入到" + fileName + "文件中");
  }
}

(3)然后创建文件服务代理类逻辑处理类 FileServiceInvocationHandler,这个类实现了 InvocationHandler 接口,并持有一个被代理类的对象。InvocationHandler 中有一个 invoke 方法,所有执行代理对象的方法都会被替换成执行 invoke 方法。然后通过反射在 invoke 方法中执行代理类的方法。在代理过程中,在执行代理类的方法前或者后可以执行自己的操作,这也是 spring aop 的主要原理。
// 文件服务代理类逻辑处理(不是真正的代理类)
public class FileServiceInvocationHandler implements InvocationHandler {

  // 文件服务对象引用
  private FileService target;

  // 用户许可名单
  private Set<String> authorizedUsers;

  public FileServiceInvocationHandler(FileService target, Set<String> authorizedUsers) {
    this.target = target;
    this.authorizedUsers = authorizedUsers;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("--- 准备调用方法:" + method.getName() + " ---");
    // 获取当前系统的用户名
    String currentUser = System.getProperty("user.name");
    // 判断用户是否存在许可名单中
    if (!authorizedUsers.contains(currentUser)) {
      throw new SecurityException("用户 " + currentUser + " 没有权限使用文件服务。");
    }
    // 调用目标服务方法
    Object result = method.invoke(target, args);
    System.out.println("--- 方法调用结束:" + method.getName()+ " ---");
    // 返回结果
    return result;
  }
}

(4)最后我们测试一下在运行时创建动态代理对象:
public class Test {
  @SneakyThrows
  public static void main(String[] args) {
    // 初始化许可用户列表
    Set<String> authorizedUsers = new HashSet<>(Arrays.asList("hangge", "user2"));
    // 创建被代理的实例对象
    FileService target = new RealFileService();
    // 创建InvocationHandler对象
    InvocationHandler fileServiceHandler = new FileServiceInvocationHandler(target, authorizedUsers);
    // 创建代理对象,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
    FileService proxy = (FileService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            fileServiceHandler
    );
    // 调用代理对象的读文件方法
    proxy.readFile("file.txt");
    // 调用代理对象的写文件方法
    proxy.writeFile("file.txt", "content");
  }
}

(5)假设当前系统用户存在于许可列表中,运行结果如下:

(6)如果当前系统用户不属于许可名单,则运行结果如下:

附:使用 Cglib 实现动态代理

1,基本介绍

(1)CglibCode Generation Library)是一个开源项目。 是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。
(2)动态代理需要被代理类实现接口,如果被代理类没有实现接口,那么这种情况就可以用到 CGLib 了。这种代理方式就叫做 CGlib 代理。
Cglib 代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理 final 类。
(3)JDK 代理与 Cglib 代理的区别:
提示:动态代理与 CGLib 动态代理都是实现 Spring AOP 的基础。如果加入容器的目标对象有实现接口,用动态代理;如果目标对象没有实现接口,用 Cglib 代理。

2,使用样例

(1)这里我们同样以安全访问控制为例演示动态代理的使用。假设我们有个文件操作类 RealDataService,只不过这次它没有实现任何接口:
注意:当然如果有接口也不影响使用 Cglib
// 文件操作类
public class RealFileService{
  public void readFile(String fileName) {
    // 实际的读取文件操作
    System.out.println("读取" + fileName + "文件内容");
  }

  public void writeFile(String fileName, String content) {
    // 实际的写入文件操作
    System.out.println("将\"" + content + "\"写入到" + fileName + "文件中");
  }
}

(2)接着定义一个拦截器。在调用目标方法时,CGLib 会回调 MethodInterceptor 接口方法拦截,来实现你自己的代理逻辑,类似于 JDK 中的 InvocationHandler 接口。
public class FileServiceInterceptor<T> implements MethodInterceptor {
  // 目标对象引用
  private T target;

  // 用户许可名单
  private Set<String> authorizedUsers;

  public FileServiceInterceptor(T target, Set<String> authorizedUsers) {
    this.target = target;
    this.authorizedUsers = authorizedUsers;
  }

  // 创建代理对象
  public Object getProxy() {
    // cglib工具类
    Enhancer en = new Enhancer();
    // 设置父类
    en.setSuperclass(this.target.getClass());
    // 设置回调函数
    en.setCallback(this);
    return en.create();
  }

  //拦截方法
  @Override
  public Object intercept(Object obj, Method method, Object[] args,
                          MethodProxy methodProxy) throws Throwable {
    System.out.println("--- 准备调用方法:" + method.getName() + " ---");
    // 获取当前系统的用户名
    String currentUser = System.getProperty("user.name");
    // 判断用户是否存在许可名单中
    if (!authorizedUsers.contains(currentUser)) {
      throw new SecurityException("用户 " + currentUser + " 没有权限使用文件服务。");
    }
    // 调用目标服务方法
    Object result = method.invoke(target, args);
    System.out.println("--- 方法调用结束:" + method.getName()+ " ---");
    // 返回结果
    return result;
  }
}

(3)最后测试一下:
public class Test {
  @SneakyThrows
  public static void main(String[] args) {
    // 初始化许可用户列表
    Set<String> authorizedUsers = new HashSet<>(Arrays.asList("hangge", "user2"));
    // 创建被代理的实例对象
    RealFileService target = new RealFileService();
    // 创建拦截器对象
    FileServiceInterceptor interceptor = new FileServiceInterceptor(target, authorizedUsers);
    // 获取代理对象
    RealFileService proxy = (RealFileService)interceptor.getProxy();
    // 调用代理对象的读文件方法
    proxy.readFile("file.txt");
    // 调用代理对象的写文件方法
    proxy.writeFile("file.txt", "content");
  }
}

(4)假设当前系统用户存在于许可列表中,运行结果如下:

(5)如果当前系统用户不属于许可名单,则运行结果如下:
评论

全部评论(0)

回到顶部