动态代理是什么

首先说下代理模式,代理模式是常见的一种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动态代理的原理的更多相关文章

  1. java高级---->Java动态代理的原理

    Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...

  2. java动态代理的原理

    在许多mvc框架中,经常用到注解来实现面向切面(aop)的编程.面向切面编程,可以对业务逻辑中各部分进行分离,提高程序的重用性,降低各逻辑业务部分的耦合度. jdk中利用反射原理使用Proxy类对对象 ...

  3. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  4. JAVA动态代理 你真的完全了解Java动态代理吗?

    网上讲JAVA动态代理,说的天花乱坠,发现一篇文章写的通俗易懂,特意转载过来 原文地址:https://www.jianshu.com/p/95970b089360 动态代理看起来好像是个什么高大上的 ...

  5. Java动态代理简单应用

    概念 代理模式是基本的设计模式之一,它是开发者为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象.这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色. Java动态代理比 ...

  6. 深入浅出Java动态代理

    文章首发于[博客园-陈树义],点击跳转到原文深入浅出Java动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理 ...

  7. Java动态代理 深度详解

    代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 今天我将用非常 ...

  8. Java动态代理:一个面包店的动态代理帝国

    文章首发于[博客园-陈树义],点击跳转到原文大白话说Java动态代理:一个面包店的动态代理帝国 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中, ...

  9. java动态代理——代理方法的假设和验证及Proxy源码分析五

    前文地址 https://www.cnblogs.com/tera/p/13419025.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...

随机推荐

  1. abp + vue 模板新建页面

    新建页面 创建按对应的模块和实体 新建的模块需要进行注册

  2. scrapy框架结构与工作原理

    组件: ENGINE:引擎,框架的核心,其他组件在其控制下协同工作. SCHEDULER:调度器,负责对SPIDER提交的下载请求进行调度 DOWNLOADER:下载器,负责下载页面,发送HTTP请求 ...

  3. Python-发送邮件验证码

    前言 ​ 关于 Python 这个栏目,咕了几个月了,今天讲讲如何发送验证码并验证. ​ 因为部分原因,写这篇文章的时候心情是不太好的,播放首歌吧. 代码 导入 导入yagmail,random和ti ...

  4. HTTPS 和 SSL/TLS 协议:密钥交换(密钥协商)算法及其原理

    转自:https://blog.csdn.net/andylau00j/article/details/54583769 本系列的前一篇,咱们聊了“密钥交换的难点”以及“证书体系”的必要性.今天这篇来 ...

  5. 洛谷 P4910 帕秋莉的手环

    题意 多组数据,给出一个环,要求不能有连续的\(1\),求出满足条件的方案数 \(1\le T \le 10, 1\le n \le 10^{18}\) 思路 20pts 暴力枚举(不会写 60pts ...

  6. 【UWP】利用EF Core操作SQLite

    在以往开发中,一定要在vs中安装SQLite for Universal App Platform以及一款wrapper,如SQLitePCL.现在有了EntitfyFramewrok Core,我们 ...

  7. 【博客搭建】Typecho个人博客搭建,快速安装,超小白(很简单的)

    使用Typecho框架一个月又十二天了,就目前感觉来说,整体还不错,很多方面都支持个性化,二次开发,但是目前MD编辑器有一丢丢问题,不能同步滚动条滚动,就是编辑器区域滚动,预览区域没有动静,需要两边都 ...

  8. 配置类需要标注@Configuration却不知原因?那这次就不能给你涨薪喽

    专注Java领域分享.成长,拒绝浅尝辄止.关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.My ...

  9. day61 django入门(2)

    目录 一.数据的查.改.删 1 查 2 改 3 删 二.django orm中如何创建表关系 三.django请求生命周期流程图 四.路由层 1 无名分组 2 有名分组 3 两种分组不能混用,单个可以 ...

  10. (转自MDN)CSS基础一定要看的包含块(containing block)

    之前在写<个人常用的水平居中方法>这篇文章的时候,百分比问题涉及到了包含块(containing block)这个概念. 今天刷面试题的时候,又看到了containing block这个词 ...