用大白话讲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动态代理,本着究根问底的 ...
随机推荐
- mackdown基础语法
目录 前言 二.Markdown基本语法 前言 由于有些语法无法在博客园展示,推荐使用Typora解锁全套,下载地址:https://www.typora.io/ Markdown是一种可以使用普通文 ...
- 关于延迟段创建-P1
文章目录 1 疑问点 2 环境创建 2.1 创建用户 2.2 创建表test 2.3 查看表的段信息 2.4 延迟段创建相关参数 1 疑问点 P1页有句话说道: 在Oracle 11.2.0.3.0以 ...
- 每天一个Linux命令(ls)
前提 文件路径的描述方式 只有1个盘,叫根目录/ 绝对路径:从根目录开始描述的文件路径描述方式,如/a/b/c.以/打头的路径描写法一定是绝对路径 相对路径:以相对于当前路径的文件路径描述 ...
- The Shortest Statement CodeForces - 1051F 最小生成树+并查集+LCA
题目描述 You are given a weighed undirected connected graph, consisting of n vertices and mm edges. You ...
- Centos 6.4最小化安装后的优化(2)
1.关闭不必要的服务 众所周知,服务越少,系统占用的资源就会越少,所以应当关闭不需要的服务器.首先可以先看下系统中存在哪些已经开启了的服务.查看命令如下: ntsysv 下面列出的是需要启动的服务器, ...
- 使用Rancher在K8S上部署高性能PHP应用程序
介 绍 PHP是网络上最流行的编程语言之一,许多被广泛使用的内容管理系统都使用它开发,如WordPress和Drupal,并为现代服务器端框架(如Laravel和Symfony)提供核心代码. 尽管P ...
- shell专题(九):函数
9.1 系统函数 1.basename基本语法 basename [string / pathname] [suffix] (功能描述:basename命令会删掉所有的前缀包括最后一个(‘/’)字 ...
- 数据可视化基础专题(六):Pandas基础(五) 索引和数据选择器(查找)
1.序言 如何切片,切块,以及通常获取和设置pandas对象的子集 2.索引的不同选择 对象选择已经有许多用户请求的添加,以支持更明确的基于位置的索引.Pandas现在支持三种类型的多轴索引. .lo ...
- 机器学习实战基础(二十六):sklearn中的降维算法PCA和SVD(七) 附录
- bzoj2648SJY摆棋子&&bzoj2716[Violet 3]天使玩偶*
bzoj2648SJY摆棋子 bzoj2716[Violet 3]天使玩偶 题意: 棋盘上有n个棋子,现在有m个操作,一种是加棋子,一种是查询离某个点最近的棋子.n,m≤500000. 题解: 先将已 ...