从源码角度分析JDK动态代理

文章目录

  • 前言
  • 一、JDK动态代理
  • 二、动态代理的生成
  • 三、invoke的运行时调用
  • 总结


前言

  本篇从源码的角度,对JDK动态代理的实现,工作原理做简要分析。


一、JDK动态代理

  JDK动态代理是运行时动态代理的一种实现,相比较于CGLIB ,目标对象必须实现接口,下面是一个简单案例:
  接口及实现类:

public interface UserService {void register();
}
public class UserServiceImpl implements UserService {@Overridepublic void register() {System.out.println("注册的逻辑...");}
}

  测试类:

public class ProxyTest {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("注册前...");Object invoke = method.invoke(userService, args);System.out.println("完成注册...");return invoke;}});proxy.register();}
}

  关键在于Proxy.newProxyInstance方法,该方法有三个参数:

  • ClassLoader loader:指定代理类的类加载器。
  • Class<?>[] interfaces:指定代理类需要实现的接口。
  • InvocationHandler h:定义代理对象的行为。

在这里插入图片描述  最关键的是第三个参数:InvocationHandler h官方注释给出的含义是,每次调用代理对象的方法时,都会转发到 InvocationHandlerinvoke 方法进行处理。
  案例中的代码运行后也确实是执行了invoke的逻辑:
在这里插入图片描述  那么动态代理是如何生成的?调用目标类的方法,为何会执行invoke的逻辑?下面从源码的角度进行分析

二、动态代理的生成

  生成动态代理的关键,在于getProxyClass0中的逻辑:
在这里插入图片描述  java.lang.reflect.Proxy#getProxyClass0的两个参数,分别是类加载器,和代理类实现的接口。首先会尝试从proxyClassCache缓存中获取代理类,如果获取不到,则会走get方法的逻辑进行创建:

    private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactory//如果存在由实现给定接口的给定加载器定义的代理类,则返回缓存的副本;否则,它将通过 ProxyClassFactory 创建代理类return proxyClassCache.get(loader, interfaces);}

  java.lang.reflect.WeakCache#get的参数,同样是类加载器,和代理类实现的接口:

    public V get(K key, P parameter) {Objects.requireNonNull(parameter);//清理缓存中已经过期或失效的条目。expungeStaleEntries();//将类加载器和refQueue包装成一个CacheKey对象,支持弱引用机制。//保证即使主键对象被垃圾回收,缓存条目也能正确清理。Object cacheKey = CacheKey.valueOf(key, refQueue);// lazily install the 2nd level valuesMap for the particular cacheKey//初始化二级缓存映射 // private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();//map是WeakCache的属性,value是二级缓存,每一级缓存都是ConcurrentMap的形式ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//二级缓存为空if (valuesMap == null) {//将一个新的 ConcurrentHashMap 放入一级缓存,使用CHM为了保证插入的并发安全,防止重复插入ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());if (oldValuesMap != null) {valuesMap = oldValuesMap;}}// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap//利用类加载器和和代理类实现的接口生成一个二级缓存keyObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));//获取二级缓存的valueSupplier<V> supplier = valuesMap.get(subKey);//初始化一个工厂Factory factory = null;while (true) {//如果二级缓存中已经有了value,直接获取if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instanceV value = supplier.get();if (value != null) {return value;}}// else no supplier in cache// or a supplier that returned null (could be a cleared CacheValue// or a Factory that wasn't successful in installing the CacheValue)// lazily construct a Factoryif (factory == null) {//懒加载一个 Factoryfactory = new Factory(key, parameter, subKey, valuesMap);}// 如果当前没有 supplierif (supplier == null) {//将当前 Factory 插入二级缓存。supplier = valuesMap.putIfAbsent(subKey, factory);//supplier 为 null 证明二级缓存中没有相同的值if (supplier == null) {// successfully installed Factory//当前线程成功设置factorysupplier = factory;}// else retry with winning supplier} else {if (valuesMap.replace(subKey, supplier, factory)) {// successfully replaced// cleared CacheEntry / unsuccessful Factory// with our Factorysupplier = factory;} else {// retry with current suppliersupplier = valuesMap.get(subKey);}}}}

  在该方法中利用到了二级缓存,关键在于WeakCache的map属性:一级缓存的value是一个ConcurrentMap类型的集合。

    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();

  当某个线程第一次进入该方法时,根据key从一级缓存中获取是为空的,就会在下面的代码片段中创建一个一级缓存一级缓存中的二级缓存此时只是初始化,是没有具体的值的:
在这里插入图片描述  然后会根据类加载器,和代理类实现的接口构建一个二级缓存的key,尝试从二级缓存中获取值:
在这里插入图片描述  第一次也是获取不到的,所以会在创建Factory工厂后,通过valuesMap.putIfAbsent(subKey, factory);去设置二级缓存的键值:
在这里插入图片描述
  接着第二次循环再次走到下面的代码片时,supplier已经不为空了(此时的supplier就是factory,二级缓存的value)
在这里插入图片描述  不难看出上述的操作都是在为缓存赋值,使用CHM的原因也是为了防止多线程并发操作时发生重复。


  接下来V value = supplier.get();实际是调用了WeakCacheFactory内部类的get方法,方法上也加了锁避免并发冲突。

        @Overridepublic synchronized V get() { // serialize access// re-check//从二级缓存中获取值Supplier<V> supplier = valuesMap.get(subKey);//还会判断supplier的类型是否被替换if (supplier != this) {// something changed while we were waiting:// might be that we were replaced by a CacheValue// or were removed because of failure ->// return null to signal WeakCache.get() to retry// the loopreturn null;}// else still us (supplier == this)// create new valueV value = null;try {value = Objects.requireNonNull(valueFactory.apply(key, parameter));} finally {if (value == null) { // remove us on failurevaluesMap.remove(subKey, this);}}// the only path to reach here is with non-null valueassert value != null;// wrap value with CacheValue (WeakReference)//用一个弱引用包装器(CacheValue)包装生成的值 value。CacheValue<V> cacheValue = new CacheValue<>(value);// try replacing us with CacheValue (this should always succeed)if (valuesMap.replace(subKey, this, cacheValue)) {// put also in reverseMap//将 cacheValue 添加到 reverseMap 中。reverseMap.put(cacheValue, Boolean.TRUE);} else {throw new AssertionError("Should not reach here");}// successfully replaced us with new CacheValue -> return the value// wrapped by it//成功生成的值(value)被缓存后,返回给调用者。return value;}}

  图上圈出的方法,是创建代理类的核心方法:
在这里插入图片描述  在java.lang.reflect.Proxy.ProxyClassFactory#apply方法中,首先会进行接口的各种校验,以及生成代理类的名称

        @Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {//验证是否存在重复接口Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {/** Verify that the class loader resolves the name of this* interface to the same Class object.*/Class<?> interfaceClass = null;try {interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}//如果通过类加载器加载的接口与传入的接口对象不一致,说明接口不可见,抛出异常。if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}/** Verify that the Class object actually represents an* interface.*///使用 isInterface() 确认当前类是一个接口。if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** Verify that this interface is not a duplicate.*///如果接口已经存在于集合中,说明重复,抛出异常。if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // package to define proxy class in//默认代理类为 public 和 finalint accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*/for (Class<?> intf : interfaces) {int flags = intf.getModifiers();//如果接口不是 public,代理类必须在与接口相同的包中。if (!Modifier.isPublic(flags)) {//将代理类的访问权限限制为 final。不可修改accessFlags = Modifier.FINAL;//对于非 public 接口,提取接口的包名。String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;//所有非 public 接口在同一个包中,否则抛出异常。} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}//如果所有接口都是 public,代理类定义在默认的 com.sun.proxy 包中。if (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*///生成代理类的名称long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Generate the specified proxy class.*///真正创建代理类的核心方法,偏底层的native方法byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}}}

  代理类的名称格式为:[包名].Proxy $ [递增编号],为什么代理类中有$符号也是在这里决定的:
在这里插入图片描述  而return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);是核心中的核心,代理类正是在该方法中生成的,点进去发现是本地方法
在这里插入图片描述  那么如何才能看到在运行时动态生成的代理类?可以添加JVM参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在这里插入图片描述  运行项目,在当前的工作目录下即可看到运行时动态生成的代理类:
在这里插入图片描述

三、invoke的运行时调用

  在上一步既然看到了生成的代理类,那么在调用目标方法时,为什么会转发到invoke就很清晰了:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.sun.proxy;import com.itbaima.proxy.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements UserService {private static Method m1;private static Method m2;private static Method m0;private static Method m3;public $Proxy0(InvocationHandler var1) throws  {super(var1);}//...中间都是一些hashcode和equals的逻辑public final void register() throws  {try {//调用Proxy类InvocationHandler属性的invoke方法super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");m3 = Class.forName("com.itbaima.proxy.UserService").getMethod("register");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(((Throwable)var2).getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(((Throwable)var3).getMessage());}}
}

  调用代理对象的 register 方法时,register 方法的调用被路由到 $Proxy0 的 register 方法,然后又会调用Proxy类InvocationHandler属性的invoke方法。


总结

  1. 为什么生成的代理类,方法都要被final修饰?
      防止子类重写,绕过 InvocationHandler 的逻辑,破坏代理机制,确保代理行为的安全性。并且生成的代理类是高度封装的,设计原则是尽量减少外界对其行为的干扰。final 修饰符保证了代理类的封闭性和完整性,符合开闭原则(对扩展开放,对修改封闭)。
  2. JDK 动态代理中的 WeakCache为什么要使用二级缓存机制?
      一级缓存存储了一个 Supplier,用来提供对代理类的引用。二级缓存存储实际生成的代理类。二级缓存的存在可以保存生成的代理类,避免因一级缓存中的数据被回收导致代理类被重复生成。并且二级缓存通过弱引用管理代理类生命周期,避免内存泄漏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/18720.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统——计算机系统概述——1.5操作系统引导(开机过程)

操作系统引导&#xff1a; A.CPU从一个特定主存地址开始&#xff0c;取指令&#xff0c;执行ROM中的引导程序&#xff08;先进行硬件自检&#xff0c;再开机&#xff09; B.将磁盘的第一块——主引导记录读入内存&#xff0c;执行磁盘引导程序&#xff0c;扫描分区表 C.从活动分…

推荐一本python学习书:《编程不难》

推荐理由 全面&#xff1a;把零基础Python编程、可视化、数学、数据、机器学习&#xff0c;融合在一起&#xff0c;循循渐进。 开源&#xff1a;PDF、Python代码、Jupyter文档&#xff0c;在github直接免费下&#xff01; 真实&#xff1a;提供大量真实场景下的数据&#xff…

数据结构与算法分析模拟试题及答案5

模拟试题&#xff08;五&#xff09; 一、单项选择题&#xff08;每小题 2 分&#xff0c;共20分&#xff09; &#xff08;1&#xff09;队列的特点是&#xff08;   &#xff09;。 A&#xff09;先进后出 B&#xff09;先进先出 C&#xff09;任意位置进出 D&#xff0…

集群聊天服务器(9)一对一聊天功能

目录 一对一聊天离线消息服务器异常处理 一对一聊天 先新添一个消息码 在业务层增加该业务 没有绑定事件处理器的话消息会派发不出去 聊天其实是服务器做一个中转 现在同时登录两个账号 收到了聊天信息 再回复一下 离线消息 声明中提供接口和方法 张三对离线的李…

jedis基础入门

jedis采用key&#xff0c;value的形式保存数据&#xff0c;使用nosql sql和nosql的区别 一&#xff1a;入门案例 导入依赖 <dependencies><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>…

QWen2.5学习

配置环境 pip install transformers 记得更新一下&#xff1a;typing_extensions pip install --upgrade typing_extensions 安装modelscope modelscope/modelscope: ModelScope: bring the notion of Model-as-a-Service to life. 下载这个仓库的代码上传到服务器解压 推…

足球青训俱乐部管理后台系统(程序+数据库+报告)

基于SpringBoot的足球青训俱乐部管理后台系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块 编程语言&#xff1a;Java 数据库&#xff1a;MySQL 项目管理工具&#xff1a;Maven 前端技术&#xff1a;Vue 后端技术&#xff1a;SpringBoot…

MoneyPrinterTurbo - AI自动生成高清短视频

MoneyPrinterTurbo是一款基于AI大模型的开源软件&#xff0c;旨在通过一键操作帮助用户自动生成高清短视频。只需提供一个视频 主题或 **关键词** &#xff0c;就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐&#xff0c;然后合成一个高清的短视频。 ​ ​ 主要…

Cross-Inlining Binary Function Similarity Detection

注&#xff1a;在阅读该论文时顺便参考了作者团队的分享视频&#xff1a;【ICSE 2024论文预讲会-第二期-下午-哔哩哔哩】 https://b23.tv/XUVAPy3 在这个视频的末尾最后一个 一.introducion 计算下面两个函数的相似度&#xff1a; 查询函数&#xff1a;脆弱函数&#xff0c;重…

C++:哈希拓展-位图

目录 一.问题导入 二.什么是位图? 2.1如何确定目标数在哪个比特位? 2.2如何存放高低位 2.3位图模拟代码实现 2.3.1如何标记一个数 2.3.2如何重置标记 2.3.3如何检查一个数是否被标记 整体代码实现 标准库的Bitset 库中的bitset的缺陷 简单应用 一.问题导入 这道…

GCP : Memcache backed by Cloud Datastore

Memcache backed by Cloud Datastore 的用途主要体现在以下几个方面&#xff1a; 提高性能和可扩展性&#xff1a; Memcache 是一个高性能的分布式内存对象缓存系统&#xff0c;通常用于缓存数据库查询等操作&#xff0c;以减轻数据库负载&#xff0c;加快动态Web应用的响应速度…

【Python】问题解决:yaml文件加载得到字符串而不是字典

问题描述 最近需要使用python处理yaml文件&#xff0c;但使用过程中发现只能输出字符串的格式&#xff0c;而不是想要的字典格式。 基本使用 在python中想要读写yaml文件&#xff0c;可以安装使用第三方包pyyaml来实现&#xff0c;首先安装一下&#xff1a; pip install pyya…

时钟之Canvas+JS版

写在前面 上一篇介绍使用CSSJS方式实现&#xff0c;但元素太过单一。此篇将以HTML5的canvas标签结合JS来实现。 HTML代码 <canvas id"clock" width"300" height"300"></canvas> JS代码 var canvas null; var ctx null; var int…

shell脚本_创建执行与变量的定义与使用

PS:前言本章节讲解使用的系统为linux2024.1&#xff0c;基于Debian的Linux发行版。 一、什么是shell脚本&#xff1f; 1. 定义&#xff1a; 2. 主要特点&#xff1a; 3. shell脚本的基本结构 4. Shebang 二、创建执行 1.脚本的创建 2. 脚本的执行 2.1.chmod 2.2. 使用…

CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃

CSP/信奥赛C语法基础刷题训练&#xff08;11&#xff09;&#xff1a;洛谷P5743&#xff1a;猴子吃桃 题目描述 一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半&#xff0c;又贪嘴多吃了一个&#xff1b;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n n n…

C++11(四)---可变参数模板

文章目录 可变参数模板 可变参数模板 参数包代表多个类型和参数 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包 // 声明一个参数包Args...args&#xff0c;这个参数包中可以包含0到任意个模板参数。 template <class ...Args> void ShowList(Args... arg…

【qt】控件1

1.控件使能&#xff08;enabled&#xff09; QPushbutton*stnew QPushbutton(this);//定义一个按钮 st->setEnabled(false);//按钮设置不能使用当设置该控件不能使用的话&#xff0c;对应控件的子控件也不能使用 通过isEnabled()函数可以查看对应控件状态 演示&#xff1…

昇思MindSpore第二课---Transformer

1. Transformer的概念 Transformer是一种基于注意力机制结构的神经网络&#xff0c;其主要的作用就是用于处理机器翻译、语言建模以及文本生成等自然语言的处理。 比如人类在做一篇阅读理解的时候&#xff0c;我们的注意力可能主要集中在我们所阅读的这一行内容。而机器也是如此…

【Go】-bufio库解读

目录 Reader和Writer接口 bufio.Reader/Writer 小结 其他函数-Peek、fill Reader小结 Writer Scanner结构体 缓冲区对于网络数据读写的重要性 Reader和Writer接口 在net/http包生成的Conn 接口的实例中有两个方法叫做Read和Write接口 type Conn interface {Read(b []b…

mac 0S中虚拟机分辨率高怎么办

在VMware Fusion安装的Windows虚拟机有时候会遇到下图的问题&#xff0c;分辨率很高、桌面和任务栏的图标都很小&#xff0c;没办法正常使用。 解决方法&#xff1a; 点击工具栏中的扳手图标&#xff0c;打开设置。 打开系统设置中的“显示器”。 取消勾选“使用Retina全分辨率…