Java - 静态代理与动态代理详解(附:Cglib的使用)
作者:hangge | 2023-01-11 08:40
代理模式(Proxy)是为目标对象提供一种代理,从而能够在不改变目标对象的情况下,对目标对象的访问进行控制。例如:访问权限的控制、访问地址的控制、访问方式的控制等。而根据代理类的创建时间又可以分为静态代理和动态代理。下面通过样例分别演示 Java 中静态代理与动态代理如何实现。
(2)接着定义一个文件操作类 RealFileService 实现这些操作方法:
(3)然后定义一个代理类 FileServiceSecurityProxy,它充当了客户端和 RealFileService 类之间的中介。FileServiceSecurityProxy 会在调用 RealFileService 类的方法之前检查当前用户是否有权限访问文件服务。如果没有权限,就会抛出 SecurityException 异常。这样就能实现安全访问控制的目的。
(4)最后测试一下:
(5)假设当前系统用户存在于许可列表中,运行结果如下:
(3)JDK 从 1.3 版本就开始支持动态代理类的创建。java.lang.reflect 包中的 Proxy 类提供了用于创建动态代理类和实例的方法。使用动态代理需要提供一个接口和一个 InvocationHandler 实例(InvocationHandler 接口也在 java.lang.reflect 包中),Proxy 类会根据这些信息生成动态代理类。
(2)接着定义一个文件操作类 RealFileService 实现这些操作方法,这个还是和上面一样:
(3)然后创建文件服务代理类逻辑处理类 FileServiceInvocationHandler,这个类实现了 InvocationHandler 接口,并持有一个被代理类的对象。InvocationHandler 中有一个 invoke 方法,所有执行代理对象的方法都会被替换成执行 invoke 方法。然后通过反射在 invoke 方法中执行代理类的方法。在代理过程中,在执行代理类的方法前或者后可以执行自己的操作,这也是 spring aop 的主要原理。
(4)最后我们测试一下在运行时创建动态代理对象:
(5)假设当前系统用户存在于许可列表中,运行结果如下:
(2)接着定义一个拦截器。在调用目标方法时,CGLib 会回调 MethodInterceptor 接口方法拦截,来实现你自己的代理逻辑,类似于 JDK 中的 InvocationHandler 接口。
(3)最后测试一下:
(4)假设当前系统用户存在于许可列表中,运行结果如下:
一、静态代理
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 ---"); } }
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)由于动态代理可以通过反射机制动态生成代理类,因此具有如下优点:
- 可以动态生成任意数量的代理类
- 代码复杂度较低
- 可以很好地扩展
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)Cglib(Code Generation Library)是一个开源项目。 是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。
(2)动态代理需要被代理类实现接口,如果被代理类没有实现接口,那么这种情况就可以用到 CGLib 了。这种代理方式就叫做 CGlib 代理。
Cglib 代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理 final 类。
(3)JDK 代理与 Cglib 代理的区别:- JDK 动态代理实现接口,Cglib 动态继承思想
- JDK 动态代理(目标对象存在接口时)执行效率高于 Cglib
- 如果对象有接口实现,选择 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)