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)