网站首页 > 文章精选 正文
1. 示例
首先,定义一个接口:
public interface Staff {
void work();
}
然后,新增一个类并实现上面的接口:
public class Coder implements Staff {
@Override
public void work() {
System.out.println("认真写bug……");
}
}
假设现在有这么一个需求:在不改动以上类代码的前提下,对该方法增加一些前置操作或者后置操作。
接下来就来讲解下,如何使用JDK动态代理来实现这个需求。
首先,自定义一个调用处理器,实现java.lang.reflect.InvocationHandler接口并重写invoke方法:
public class AttendanceInvocationHandler implements InvocationHandler {
private final Object target;
public AttendanceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("上班打卡……");
Object invoke = method.invoke(target, args);
System.out.println("下班打卡……");
return invoke;
}
}
重点看下Object invoke = method.invoke(target, args);,该行代码会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。
然后,新建个测试类,看下JDK动态代理如何使用:
public class JdkProxyTest {
public static void main(String[] args) {
Coder coder = new Coder();
AttendanceInvocationHandler h = new AttendanceInvocationHandler(coder);
// 创建代理对象
Object proxyInstance = Proxy.newProxyInstance(coder.getClass().getClassLoader(),
coder.getClass().getInterfaces(),
h);
Staff staff = (Staff) proxyInstance;
staff.work();
}
}
运行以上代码,效果如下图所示:
从运行结果可以看出,在目标方法的前后,执行了自定义的操作。
2. 原理
这里理解2个概念,目标对象和代理对象,
目标对象是真正要调用的对象,上面示例中的Coder类就是目标对象,
代理对象是JDK自动生成的对象,在代理对象内部会去调用目标对象的目标方法。
JDK动态代理的核心就是上面示例中的Proxy.newProxyInstance方法,方法签名如下图所示:
第1个参数传入的是目标对象的ClassLoader,第2个参数传入的是目标对象的接口信息,第3个参数传入的是自定义的InvocationHandler。
然后看下该方法的实现逻辑,先看第1处重点:
注释翻译过来是:查找或者生成指定的代理类。
该方法会生成代理类的字节码文件(也可能是从缓存中读取),核心逻辑在ProxyClassFactory类的apply方法中,
该方法中定义了生成的代理类的包名以及文件名:
因此默认情况下,自动生成的代理类名称是com.sun.proxy.$Proxy0。
该方法最后会生成代理类的字节码,默认情况下不会保存到文件系统,但可以通过参数指定保存到文件系统:
可以看出,保存不保存到文件系统,受saveGeneratedFiles的影响,其定义如下所示:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
所以可以通过指定sun.misc.ProxyGenerator.saveGeneratedFiles参数来让生成的代理类字节码文件保存到文件系统中。
然后看第2处重点:
先是获取构造函数,然后是生成代理类对象的实例。
3. 为什么必须要基于接口?
思考一个问题,为什么JDK动态代理必须要基于接口,带着这个问题,我们看下动态生成的代理类com.sun.proxy.$Proxy0长什么样子?
JVM参数里添加参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,然后启动上面示例中的测试代码:
生成的代理类字节码文件保存在项目根目录下的com/sun/proxy目录下:
在IDEA中打开后,如下图所示:
在静态代码块中,对静态变量m0、m1、m2、m3进行了赋值,其中m3是要执行的目标方法。
在构造方法中,执行的是super(var1);,也就是父类Proxy的构造方法:
该方法是将我们自定义的InvocationHandler赋值给了父类的变量h。
而以下测试代码实际执行的是代理类$Proxy0里的work方法:
Staff staff = (Staff) proxyInstance;
staff.work();
代理类$Proxy0里的work方法实际执行的是自定义InvocationHandler里的invoke方法:
因此在执行目标方法前后,执行了自定义的前置操作和后置操作。
了解了这个调用过程,就理解了为什么JDK动态代理必须要基于接口,因为动态生成的代理类已经继承了类java.lang.reflect.Proxy,
而Java又是单继承的,如果想要继续对类进行扩展,只能通过实现接口的方式。
猜你喜欢
- 2024-12-30 简单的使用SpringBoot整合SpringSecurity
- 2024-12-30 Spring Security 整合OAuth2 springsecurity整合oauth2+jwt+vue
- 2024-12-30 DeepSeek-Coder-V2震撼发布,尝鲜体验
- 2024-12-30 一个数组一行代码,Spring Security就接管了Swagger认证授权
- 2024-12-30 简单漂亮的(图床工具)开源图片上传工具——PicGo
- 2024-12-30 Spring Boot(十一):Spring Security 实现权限控制
- 2024-12-30 绝了!万字搞定 Spring Security,写得太好了
- 2024-12-30 SpringBoot集成Spring Security springboot集成springsecurity
- 2024-12-30 SpringSecurity密码加密方式简介 spring 密码加密
- 2024-12-30 Spring cloud Alibaba 从入门到放弃
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)