当前位置: 首页 > news >正文

你真以为动态生成类只能“塞进 ClassLoader 里永久住下”?隐藏类凭什么能“生成即隐身”?

👋你好,欢迎来到我的博客!我是【菜鸟不学编程】
我是一个正在奋斗中的职场码农,步入职场多年,正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上,我决定记录下自己的学习与成长过程,也希望通过博客结识更多志同道合的朋友。

🛠️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等,也会分享一些踩坑经历与面试复盘,希望能为还在迷茫中的你提供一些参考。
💡 我相信:写作是一种思考的过程,分享是一种进步的方式。

如果你和我一样热爱技术、热爱成长,欢迎关注我,一起交流进步!

全文目录:

    • I. 隐藏类概念:Java 15 的动态类加载
    • II. `Lookup.defineHiddenClass`:字节码定义
      • 1)基本调用骨架
        • 关键参数解释(别背,理解就行)
    • III. 使用场景:Lambda 代理和框架内部类
      • 1)Lambda 相关实现
      • 2)框架内部生成类:代理/访问器/桥接类
    • IV. 与匿名类比较:不可枚举和卸载
      • 1)匿名类:编译期产物(或者至少是“普通类”)
      • 2)隐藏类:运行时定义 + 不可枚举
    • V. 反射限制:隐藏类的访问控制
      • 1)不可枚举 ≠ 完全不可访问
      • 2)访问控制:Lookup 权限是核心
    • VI. 示例:动态生成的辅助类(一个“能跑通思路”的最小演示)
      • 怎么把这个示例补全成“可运行”?
    • 实战小结:隐藏类到底解决了什么?
    • 📝 写在最后

I. 隐藏类概念:Java 15 的动态类加载

Hidden Classes(JEP 371)解决的核心问题其实很明确:

框架/运行时需要生成很多短命的辅助类,但它们不应该成为“应用的公共类型系统的一部分”。

隐藏类的典型特征(你记住这几条就够用了):

  • 不可枚举(non-discoverable):常规的类发现/枚举看不到它(比如某些“列出所有已加载类”的路径)
  • 更容易卸载:它的生命周期更倾向于“跟着 Lookup/生成方走”,不强行绑死成公开类型
  • 通常不用于被外部直接引用:它更像框架内部的“实现细节”
  • 定义方式更偏“低层”:通过MethodHandles.Lookup来 define,而不是走传统ClassLoader#defineClass

一句人话总结:
**隐藏类就是给 JVM 内部/框架动态生成的“私有小工具类”。**用完最好能卸载,外人最好别摸到。

II.Lookup.defineHiddenClass:字节码定义

核心 API 在MethodHandles.Lookup上:

  • defineHiddenClass(byte[] bytes, boolean initialize, Lookup.ClassOption... options)

你可以把它理解为:

“我有一段 class 字节码,我希望 JVM 把它当成一个隐藏类定义出来,并且它和我的 lookup 权限绑定。”

1)基本调用骨架

importjava.lang.invoke.MethodHandles;importjava.lang.invoke.MethodHandles.Lookup;publicclassHiddenClassBasics{publicstaticClass<?>define(byte[]classBytes)throwsIllegalAccessException{Lookuplookup=MethodHandles.lookup();LookuphiddenLookup=lookup.defineHiddenClass(classBytes,true,// initialize: 是否立即初始化Lookup.ClassOption.NESTMATE// 常用选项:让隐藏类成为同 nest 的成员);returnhiddenLookup.lookupClass();}}
关键参数解释(别背,理解就行)
  • classBytes:你生成/改写后的 class 文件字节码
  • initialize:是否触发<clinit>
  • ClassOption.NESTMATE:让隐藏类成为当前类的“nestmate”(同一个 nest),从而可以更自然地访问 private 成员(前提仍受规则约束)

小提醒:字节码生成你可以用 ASM/Javassist/Byte Buddy。
这里我不直接塞一坨“完整 ASM 生成 class 文件”的长代码,原因很现实:那样读者容易迷失在细节里。示例部分我会给一个“最小可运行”的思路,确保读者能把主线跑通🙂。

III. 使用场景:Lambda 代理和框架内部类

隐藏类并不是为了让业务开发者天天手写defineHiddenClass,它更多是 JVM/框架的“底层武器”。典型场景:

1)Lambda 相关实现

Lambda 在底层会生成一些辅助类/方法句柄结构。隐藏类提供了更合适的载体:

  • 不污染类命名空间
  • 不需要被普通反射发现
  • 生命周期可控(配合类卸载)

2)框架内部生成类:代理/访问器/桥接类

比如:

  • ORM 生成的实体增强器
  • 序列化框架生成的访问器(快速读写字段)
  • RPC 生成的 stub/skeleton
  • DI 容器生成的工厂/代理类

这些类往往只服务于框架内部,不应该在你应用的“公共 API 世界”里占坑

隐藏类的价值就出来了:

  • “内部实现细节”更像真的实现细节
  • 让调试/诊断时可见的类集合更干净
  • 让元空间压力更可控(当然也要看你是否持有强引用)

IV. 与匿名类比较:不可枚举和卸载

很多人会问:那它和匿名类(anonymous class)有啥区别?
这个问题问得很对,因为两者都“看起来像临时生成的”。

1)匿名类:编译期产物(或者至少是“普通类”)

匿名类是编译期生成的普通 class 文件(Outer$1.class这种)。它:

  • 仍然是一个“正常可发现”的类
  • 仍然在类加载器里按普通类生命周期管理
  • 它会出现在反射、栈追踪、某些诊断输出里

2)隐藏类:运行时定义 + 不可枚举

隐藏类:

  • 定义时就带“隐藏属性”
  • 更倾向于不被常规枚举看到(non-discoverable)
  • 生命周期更贴近框架内部用途,更容易跟随 GC/卸载策略走(但注意:你如果把它的 Class 对象或 MethodHandle 长期强引用,它也不会神奇消失

一句话的差异:
匿名类是“临时写法但公开存在”,隐藏类是“临时存在但尽量不公开”。

V. 反射限制:隐藏类的访问控制

这块是很多人第一次用隐藏类时会“哎?怎么反射不到?”的地方。
隐藏类的设计目标之一就是:减少被外部反射随意摸到的可能,至少让它不再像普通类那样“随便找、随便调”。

1)不可枚举 ≠ 完全不可访问

  • 你如果已经拿到了它的Class<?>引用(比如 defineHiddenClass 的返回值),你仍然可以在一定范围内使用它
  • 但它不会轻易出现在“列出全部类”的渠道里
  • 某些反射/访问路径会更受限制(尤其当你试图把它当成公共类型暴露给外部)

2)访问控制:Lookup 权限是核心

隐藏类和Lookup绑定更紧,这意味着:

  • 谁定义的、谁能访问,在权限模型里更清晰
  • 很多能力更偏向MethodHandles世界,而不是传统反射世界
  • 对框架作者来说,这是好事:更可控、更安全
  • 对“想用反射瞎搞”的人来说……就没那么爽了😅

VI. 示例:动态生成的辅助类(一个“能跑通思路”的最小演示)

我们做一个非常现实的需求:

我希望生成一个“辅助类”,它提供一个静态方法hello(String),返回"hi, <name>"
然后我用defineHiddenClass把它定义出来,并通过 MethodHandle 调用它。

为了把重点放在 Hidden Class 本身,我用“预编译好的 class bytes”这个思路演示(工程里你可以替换成 ASM/ByteBuddy 动态生成)。下面这段代码展示关键流程:定义、取 lookupClass、findStatic、invoke。

注意:真实生成字节码的部分你可以接前面“字节码操作”章节的 ASM 生成器。这里我把“生成 bytes”抽成一个函数,重点不跑偏。

importjava.lang.invoke.MethodHandle;importjava.lang.invoke.MethodHandles;importjava.lang.invoke.MethodType;importjava.lang.invoke.MethodHandles.Lookup;publicclassHiddenClassDemo{publicstaticvoidmain(String[]args)throwsThrowable{byte[]bytes=generateHelperClassBytes();// 你可以用 ASM/ByteBuddy 实现它Lookuplookup=MethodHandles.lookup();Lookuphidden=lookup.defineHiddenClass(bytes,true,Lookup.ClassOption.NESTMATE);Class<?>hc=hidden.lookupClass();System.out.println("hidden class: "+hc.getName());MethodHandlemh=hidden.findStatic(hc,"hello",MethodType.methodType(String.class,String.class));Stringr=(String)mh.invokeExact("Ophelia");System.out.println(r);}// 这里用占位:真正实现建议用 ASM 生成一个含 public static String hello(String) 的类privatestaticbyte[]generateHelperClassBytes(){thrownewUnsupportedOperationException("Implement with ASM/ByteBuddy to return valid class bytes");}}

怎么把这个示例补全成“可运行”?

你有两条更靠谱的路(我给你讲人话,不绕):

  1. 用 Byte Buddy:更容易生成一个简单 class(写起来像 Java)
  2. 用 ASM:更底层,但你已经有“字节码操作”那章做铺垫

如果你选 Byte Buddy,基本就是“定义一个类 + 定义一个静态方法 + 生成 bytes”。
然后把bytes丢给defineHiddenClass,后面调用逻辑不变。

你要我在这里直接贴完整 Byte Buddy 版本也可以,但我得先确认:你系列文章里是否允许引入第三方依赖?(ASM 你前面已经提了,Byte Buddy 你有没有打算讲?)🙂

实战小结:隐藏类到底解决了什么?

你可以把隐藏类理解成:JVM 给框架作者的“内部实现利器”。它把动态类从“公共世界的永久居民”变成“内部世界的临时工具”,带来的好处是:

  • 更干净:不污染公共类型系统,不容易被随意枚举
  • 更可控:与 Lookup 权限绑定,访问路径更明确
  • 更贴用途:适合 lambda/代理/访问器这类短命辅助类
  • 更利于卸载:减少长期占用元空间的概率(前提:别强引用它)

最后送你一句我自己用来判断“要不要隐藏类”的反问(挺管用):

这个动态生成的类,未来会不会被业务代码当成“类型”来依赖?

  • 如果会:它就不该是隐藏类(你需要一个正常类/可见代理)
  • 如果不会:隐藏类很可能是更合适的选择🙂

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 Java 老兵
📅 日期:2025-08-25
🧵 本文原创,转载请注明出处。

http://www.jsqmd.com/news/987820/

相关文章:

  • 2026年上海/深圳/杭州/广州情感危机干预机构客观排行+避坑指南+高频FAQ - 互联网科技品牌测评
  • TimesFM 2.5高效模型压缩实战:从500M到200M的智能瘦身方案
  • 2026上海专业劳力士回收门店实测:收的顶中检持证鉴定、私密交易、资金实时到账 - 奢侈品回收评测
  • CUTLASS终极指南:零基础掌握GPU高性能矩阵计算
  • 18.5【保姆级教程】用队列进行模拟:从数据结构到现实世界的“预言机”
  • PlayStation 3模拟器终极指南:如何在现代电脑上重温PS3经典游戏
  • 石家庄车灯升级门店排行:5家正规机构实测对比 - 起跑123
  • 2026东莞配眼镜产品口碑全解析:瞳壤五款真实体验深度测评 - 配眼镜新资讯
  • 2026年儿童竹蜻蜓厂家选型指南:产品、品质与供货能力三维度解析 - 企师傅推荐官
  • 如何在5分钟内免费生成高质量3D资产?Hunyuan3D-2终极指南
  • 如何快速上手Duix Avatar:打造专属AI数字人的完整实践指南
  • 2026定制竹蜻蜓厂家推荐:金华市精彩塑胶制品有限公司,聚焦儿童玩具与文旅礼品定制配套 - 企师傅推荐官
  • sync.Pool 的真正分界线不是对象大小——一次 benchmark 翻车记录
  • 2026年稻花香源头厂家/产地直供排行榜:五常稻花香2号/正宗优质稻花香大米最新精选推荐 - 企业推荐官【官方】
  • 成都钻石回收套路拆解,虚标高报价、刻意压低 4C 等级猫腻曝光 - 奢侈品回收评测
  • 你还在用 `+ “\\n“` 拼多行字符串吗?Java 的文本块都已经能让代码“像人写的”了!
  • 2026年郑州航空港区长短途搬家运输公司:设备搬迁、企业搬迁、机场货物搬卸分析报告 - 品研笔录
  • 无代码测试革命:Hercules如何用AI重构软件质量保障体系
  • Claudian插件终极指南:如何用AI助手提升Obsidian知识管理效率
  • 深入理解 Apache Flink 可扩展状态
  • 石家庄专业车灯升级门店排行 资质与服务实测对比 - 起跑123
  • 【信息科学与工程学】计算机科学与自动化/控制——第九十二篇 自动化控制01
  • 2026东莞GEO优化公司实力排名!实测技术、案例、效果综合对比 - 新闻快传
  • 2026防爆型气体采样探头厂家排行榜:工业安全监测核心部件选购攻略 - 品研笔录
  • 2026年必备收藏:解决AIGC烦恼的免费实用网站
  • 2026 纺织服饰配套优选:复合型高周波热转印标定制厂家严选 - 变量人生001
  • N_m3u8DL-RE终极指南:3步破解流媒体下载难题
  • 5、【AI产品经理概述】行业现状与职业前景
  • 2026年 重庆摆闸/人行通道闸/三辊闸/翼闸最新推荐榜单:厂家实力与稳定耐用的选型指南 - 品牌发掘
  • 2026上海高端手表回收:江诗丹顿回收市场行情解析 - 奢侈品回收评测