用大白话讲Java动态代理的原理
动态代理是什么
首先说下代理模式,代理模式是常见的一种java设计模式,特征是代理类与委托类实现了同样的接口,代理类主要负责为委托类预处理、过滤、转发,以及事后处理等。代理类与委托类之间通常会存在关联关系,一个代理类的实例与它的委托类的实例是关联的。代理类的实例本身是并不真正关心被调用方法的内部逻辑,而是会通过内部访问调用 委托类的实例真正实现了的方法,来为调用者提供服务。
有代理的话,在访问实际对象时,是通过代理实例来访问、调用委托类方法的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
动态代理对比静态代理,最大的特点是代理类是在程序运行时生成的,并非在编译期生成,能做的事情也多了,自然风险也高了。
动态代理最简单的用法
用一个比较接近生活的例子:中午,饿了的室友 委托 持家有道的你 去点外卖
Hungry.java :接口
public interface Hungry {
void callLunch();
}
Roommate.java :Hungry接口的实现类,也就是委托类
public class Roommate implements Hungry{
private String name;
public Roommate(String name) {
this.name = name;
}
@Override
public void callLunch() {
System.out.println("好饿,今天午饭点外卖吧");
}
}
public class RoommateInvocationHandler<T> implements InvocationHandler {
private T rommate;
public RoommateInvocationHandler(T roommate){
this.rommate = roommate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("下单前,我先帮你看下有没有平台优惠券吧");
Object result = method.invoke(rommate , args);
return result;
}
}
InvocationHandler是一个接口,由代理实例内部的invocation handler实现的接口。每个代理实例都有一个关联的invocation handler。当代理实例上调用方法时,method.invoke(baseImpl, args),此方法将被编码并织入到代理实例内部的 invocation handler实现的invoke方法中。
利用 Proxy 的方式实现动态代理,调用 委托类接口 的方法,完成午餐点外卖这个操作
public static void main(String[] args) {
Roommate roommate = new Roommate("zhangsan");
Hungry proxyInstance = (Hungry) Proxy.newProxyInstance(
roommate.getClass().getClassLoader(),
roommate.getClass().getInterfaces(),
new RoommateInvocationHandler<Roommate>(roommate)
);
proxyInstance.callLunch();
}
//输出结果
下单前,我先帮你看下有没有平台优惠券吧
好饿,今天午饭点外卖吧
代理实例proxyInstance的类型是Hungry,所以只能调用Hungry里规定的方法。Roommate作为接口实现类,不是来自接口的其他的方法,是无法通过动态代理调用的。
可以看到代理实例在调用委托类实现的方法时,可以很方便地在调用方法的前后执行一些操作,在示例代码中则是在调用方法前简单输出了一行: System.out.println("下单前,我先帮你看下有没有平台优惠券吧"),还可以有其他用途,例如记录这个方法的耗时时间,对方法的参数或者返回结果进行修改等等。这也是Spring,Dagger进行AOP编程的原理。
那为什么继承InvocationHandler接口和持有委托类引用的RoommateInvocationHandler调用来自Hungry接口的callLunch()方法时可以调用到委托类对callLunch()的逻辑实现呢,看看它的背后原理:
动态代理的实现原理
从Proxy.newProxyInstance() 入手,逐步分析 InvocationHandler 如何建立代理实例和委托实例的关联:
public static Object newProxyInstance(ClassLoader loader , Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//InvocationHandler必须非空,说明是个重要角色
Objects.requireNonNull(h);
//获取委托类的接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* 核心:通过类加载器和委托类接口,在内存中查找出或者生成指定的代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 利用指定的invocation handler调用它的构造器方法,构建代理类的实例返回
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
来到这一步好像就停下了,那么接下来探究 cl 这个实例创建过程发生了什么:
在上面示例代码main函数的后面接着补充。利用ProxyGenerator.generateProxyClass生成这个动态生成的类文件,写入了指定路径的class文件内
“$Proxy0” 是 代理类在系统内部的编号,在示例代码只生成了一个代理类所以编号是 $Proxy0 。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces());
String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class";
try(FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(classFile);
fos.flush();
}catch (IOException e){
e.printStackTrace();
System.out.println("error:写入文件");
}
使用反编译工具,我这里用的是jd-gui反编译,这个$Proxy0类,实现了Proxy类,继承了和委托类相同的接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy_test.Hungry;
public final class $Proxy0 extends Proxy implements Hungry{
private static Method m1;
private static Method m3; //由下方静态代码块得知,m3代表callLunch()这一个方法
private static Method m2;
private static Method m0;
/*
* 父类Proxy的构造器,其中 h 属性为 InvocationHandler引用
* protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
*/
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
//关键!可供外界调用,方法名与委托类实现接口的方法相同,利用 InvocationHandler调用invoke
public final void callLunch() throws {
try{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError){
throw localError;
}
catch (Throwable localThrowable){
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject) throws {}
public final String toString() throws {...}
public final int hashCode() throws {...}
static{
try{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException){
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException){
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
事情逐渐明朗起来,从这个动态类的源码,可以分析出: $Proxy0 ,在构建这个类时,会调用了父类Proxy的构造方法,将InvocationHandler引用传递给了父类Proxy的 h 属性,于是当我们在外界使用 代理实例 调用了 callLunch() 这个方法时,就会来到这一句 this.h.invoke(this, m3, null); 由于h属性其实是InvocationHandler引用,调用了它的invoke,也就导致了上面示例代码中的RoommateInvocationHandler类的重写过的invoke方法也就被调用了,RoommateInvocationHandler也持有委托类的引用,所以委托类的方法也被调用起来了。
Java的继承机制是单继承,多接口。代理类因为必须要继承Proxy类,所以java的动态代理只能对接口进行代理,无法对一个class类进行动态代理。
动态代理原理总结
用大白话的方式讲:
有一个类InvocationHandler,它的性质类似一个中介,中介类构建时持有了委托对象,所以可以在它的invoke方法中调用了委托对象实现接口的具体方法。当外部调用这个InvocationHandler的invoke方法时,对 invoke 的调用最终都转为对委托对象的方法调用。
创建明面上负责代理的代理实例时,在内存中动态生成的类不但继承了Proxy,也实现了与委托对象相同的接口,因此代理实例可以调用此接口的方法,然后通过持有的中介类对象来调用中介类对象的invoke方法,最终达到代理实例执行了委托者的方法。
用大白话讲Java动态代理的原理的更多相关文章
- java高级---->Java动态代理的原理
Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...
- java动态代理的原理
在许多mvc框架中,经常用到注解来实现面向切面(aop)的编程.面向切面编程,可以对业务逻辑中各部分进行分离,提高程序的重用性,降低各逻辑业务部分的耦合度. jdk中利用反射原理使用Proxy类对对象 ...
- java动态代理原理
我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...
- JAVA动态代理 你真的完全了解Java动态代理吗?
网上讲JAVA动态代理,说的天花乱坠,发现一篇文章写的通俗易懂,特意转载过来 原文地址:https://www.jianshu.com/p/95970b089360 动态代理看起来好像是个什么高大上的 ...
- Java动态代理简单应用
概念 代理模式是基本的设计模式之一,它是开发者为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象.这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色. Java动态代理比 ...
- 深入浅出Java动态代理
文章首发于[博客园-陈树义],点击跳转到原文深入浅出Java动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理 ...
- Java动态代理 深度详解
代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 今天我将用非常 ...
- Java动态代理:一个面包店的动态代理帝国
文章首发于[博客园-陈树义],点击跳转到原文大白话说Java动态代理:一个面包店的动态代理帝国 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中, ...
- java动态代理——代理方法的假设和验证及Proxy源码分析五
前文地址 https://www.cnblogs.com/tera/p/13419025.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
随机推荐
- vi/vim 行删除操作
好东西就要多用,vi/vim越用越喜欢了. 删除多行怎么办呢? 命令输入“:32,65d”,回车键,32-65行就被删除了.
- python_Linux系统的常用命令(三)
用户权限常用命令 1.用户和权限 r--读--4, w--写--2, x--执行--1 chmod 可以修改用户/组对文件/目录的权限 格式:chmod +/-rwx 文件名/目录名 su -用户:切 ...
- 前段人员必藏的7 个 CSS 好用的属性绝对干货
学习CSS是构建好看网页的一种方式. 但是,在学习过程中,我们倾向于(大部分时间)限制自己,一遍又一遍地使用相同的属性. 毕竟,我们是一种习惯性的动物,我们会使用自己习惯且熟悉的东西. 因此,在这篇文 ...
- SpringCloud系列之集成分布式事务Seata应用篇
目录 前言 项目版本 项目说明 Seata服务端部署 Seata客户端集成 cloud-web module-order module-cart module-goods module-wallet ...
- P1330 封锁阳光大学——深度优先搜索DFS
P1330 封锁阳光大学 题目描述 曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街.河蟹看到欢快的曹,感到不爽.河蟹决定封锁阳光大学,不让曹刷街. 阳光大学的校园是一张由 \(n ...
- Cow Relays,过N条边的最短路
题目链接 题意: 找从a到b的经过N条边的最短路 分析: 有点板子...方法:矩阵存,然后有个类似快速幂的思想,然后再加上离散化就好了. 没啥写的,只能说说矩阵了,我用的方法是先枚举i,j再枚举k,当 ...
- 文件的f.seek和文件修改方式以及函数的基本使用
1.文件f.seek的应用 import time with open('access.log', mode='rb') as f: # 1.将指针跳到文件末尾 # f.read() # 错误 f.s ...
- Linux系统安装JDK8
一.卸载现用的JDK 1.查看Linux自带的JDK是否已安装 查看是否安装openjdk,java -version (yum安装的 一般都是 OpenJDK 命令:yum install ...
- linux专题(五):常用的基本命令(三)文件内容查看
http://dwz.date/UDf 概述 Linux系统中使用以下命令来查看文件的内容: cat 由第一行开始显示文件内容 tac 从最后一行开始显示,可以看出 tac 是 cat 的倒着写! n ...
- How to install nginx in Ubuntu
The steps for installing the nginx on Ubuntu below. 1.install the packages first. apt-get install gc ...