你好欢迎来到我的博客我是【菜鸟不学编程】我是一个正在奋斗中的职场码农步入职场多年正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上我决定记录下自己的学习与成长过程也希望通过博客结识更多志同道合的朋友。️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等也会分享一些踩坑经历与面试复盘希望能为还在迷茫中的你提供一些参考。 我相信写作是一种思考的过程分享是一种进步的方式。如果你和我一样热爱技术、热爱成长欢迎关注我一起交流进步全文目录I. 隐藏类概念Java 15 的动态类加载II. Lookup.defineHiddenClass字节码定义1基本调用骨架关键参数解释别背理解就行III. 使用场景Lambda 代理和框架内部类1Lambda 相关实现2框架内部生成类代理/访问器/桥接类IV. 与匿名类比较不可枚举和卸载1匿名类编译期产物或者至少是“普通类”2隐藏类运行时定义 不可枚举V. 反射限制隐藏类的访问控制1不可枚举 ≠ 完全不可访问2访问控制Lookup 权限是核心VI. 示例动态生成的辅助类一个“能跑通思路”的最小演示怎么把这个示例补全成“可运行”实战小结隐藏类到底解决了什么 写在最后I. 隐藏类概念Java 15 的动态类加载Hidden ClassesJEP 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{LookuplookupMethodHandles.lookup();LookuphiddenLookuplookup.defineHiddenClass(classBytes,true,// initialize: 是否立即初始化Lookup.ClassOption.NESTMATE// 常用选项让隐藏类成为同 nest 的成员);returnhiddenLookup.lookupClass();}}关键参数解释别背理解就行classBytes你生成/改写后的 class 文件字节码initialize是否触发clinitClassOption.NESTMATE让隐藏类成为当前类的“nestmate”同一个 nest从而可以更自然地访问 private 成员前提仍受规则约束小提醒字节码生成你可以用 ASM/Javassist/Byte Buddy。这里我不直接塞一坨“完整 ASM 生成 class 文件”的长代码原因很现实那样读者容易迷失在细节里。示例部分我会给一个“最小可运行”的思路确保读者能把主线跑通。III. 使用场景Lambda 代理和框架内部类隐藏类并不是为了让业务开发者天天手写defineHiddenClass它更多是 JVM/框架的“底层武器”。典型场景1Lambda 相关实现Lambda 在底层会生成一些辅助类/方法句柄结构。隐藏类提供了更合适的载体不污染类命名空间不需要被普通反射发现生命周期可控配合类卸载2框架内部生成类代理/访问器/桥接类比如ORM 生成的实体增强器序列化框架生成的访问器快速读写字段RPC 生成的 stub/skeletonDI 容器生成的工厂/代理类这些类往往只服务于框架内部不应该在你应用的“公共 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[]bytesgenerateHelperClassBytes();// 你可以用 ASM/ByteBuddy 实现它LookuplookupMethodHandles.lookup();Lookuphiddenlookup.defineHiddenClass(bytes,true,Lookup.ClassOption.NESTMATE);Class?hchidden.lookupClass();System.out.println(hidden class: hc.getName());MethodHandlemhhidden.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);}}怎么把这个示例补全成“可运行”你有两条更靠谱的路我给你讲人话不绕用 Byte Buddy更容易生成一个简单 class写起来像 Java用 ASM更底层但你已经有“字节码操作”那章做铺垫如果你选 Byte Buddy基本就是“定义一个类 定义一个静态方法 生成 bytes”。然后把bytes丢给defineHiddenClass后面调用逻辑不变。你要我在这里直接贴完整 Byte Buddy 版本也可以但我得先确认你系列文章里是否允许引入第三方依赖ASM 你前面已经提了Byte Buddy 你有没有打算讲实战小结隐藏类到底解决了什么你可以把隐藏类理解成JVM 给框架作者的“内部实现利器”。它把动态类从“公共世界的永久居民”变成“内部世界的临时工具”带来的好处是更干净不污染公共类型系统不容易被随意枚举更可控与 Lookup 权限绑定访问路径更明确更贴用途适合 lambda/代理/访问器这类短命辅助类更利于卸载减少长期占用元空间的概率前提别强引用它最后送你一句我自己用来判断“要不要隐藏类”的反问挺管用这个动态生成的类未来会不会被业务代码当成“类型”来依赖如果会它就不该是隐藏类你需要一个正常类/可见代理如果不会隐藏类很可能是更合适的选择 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者某个被流“治愈”过的 Java 老兵 日期2025-08-25 本文原创转载请注明出处。
你真以为动态生成类只能“塞进 ClassLoader 里永久住下”?隐藏类凭什么能“生成即隐身”?
发布时间:2026/6/10 20:16:48
你好欢迎来到我的博客我是【菜鸟不学编程】我是一个正在奋斗中的职场码农步入职场多年正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上我决定记录下自己的学习与成长过程也希望通过博客结识更多志同道合的朋友。️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等也会分享一些踩坑经历与面试复盘希望能为还在迷茫中的你提供一些参考。 我相信写作是一种思考的过程分享是一种进步的方式。如果你和我一样热爱技术、热爱成长欢迎关注我一起交流进步全文目录I. 隐藏类概念Java 15 的动态类加载II. Lookup.defineHiddenClass字节码定义1基本调用骨架关键参数解释别背理解就行III. 使用场景Lambda 代理和框架内部类1Lambda 相关实现2框架内部生成类代理/访问器/桥接类IV. 与匿名类比较不可枚举和卸载1匿名类编译期产物或者至少是“普通类”2隐藏类运行时定义 不可枚举V. 反射限制隐藏类的访问控制1不可枚举 ≠ 完全不可访问2访问控制Lookup 权限是核心VI. 示例动态生成的辅助类一个“能跑通思路”的最小演示怎么把这个示例补全成“可运行”实战小结隐藏类到底解决了什么 写在最后I. 隐藏类概念Java 15 的动态类加载Hidden ClassesJEP 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{LookuplookupMethodHandles.lookup();LookuphiddenLookuplookup.defineHiddenClass(classBytes,true,// initialize: 是否立即初始化Lookup.ClassOption.NESTMATE// 常用选项让隐藏类成为同 nest 的成员);returnhiddenLookup.lookupClass();}}关键参数解释别背理解就行classBytes你生成/改写后的 class 文件字节码initialize是否触发clinitClassOption.NESTMATE让隐藏类成为当前类的“nestmate”同一个 nest从而可以更自然地访问 private 成员前提仍受规则约束小提醒字节码生成你可以用 ASM/Javassist/Byte Buddy。这里我不直接塞一坨“完整 ASM 生成 class 文件”的长代码原因很现实那样读者容易迷失在细节里。示例部分我会给一个“最小可运行”的思路确保读者能把主线跑通。III. 使用场景Lambda 代理和框架内部类隐藏类并不是为了让业务开发者天天手写defineHiddenClass它更多是 JVM/框架的“底层武器”。典型场景1Lambda 相关实现Lambda 在底层会生成一些辅助类/方法句柄结构。隐藏类提供了更合适的载体不污染类命名空间不需要被普通反射发现生命周期可控配合类卸载2框架内部生成类代理/访问器/桥接类比如ORM 生成的实体增强器序列化框架生成的访问器快速读写字段RPC 生成的 stub/skeletonDI 容器生成的工厂/代理类这些类往往只服务于框架内部不应该在你应用的“公共 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[]bytesgenerateHelperClassBytes();// 你可以用 ASM/ByteBuddy 实现它LookuplookupMethodHandles.lookup();Lookuphiddenlookup.defineHiddenClass(bytes,true,Lookup.ClassOption.NESTMATE);Class?hchidden.lookupClass();System.out.println(hidden class: hc.getName());MethodHandlemhhidden.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);}}怎么把这个示例补全成“可运行”你有两条更靠谱的路我给你讲人话不绕用 Byte Buddy更容易生成一个简单 class写起来像 Java用 ASM更底层但你已经有“字节码操作”那章做铺垫如果你选 Byte Buddy基本就是“定义一个类 定义一个静态方法 生成 bytes”。然后把bytes丢给defineHiddenClass后面调用逻辑不变。你要我在这里直接贴完整 Byte Buddy 版本也可以但我得先确认你系列文章里是否允许引入第三方依赖ASM 你前面已经提了Byte Buddy 你有没有打算讲实战小结隐藏类到底解决了什么你可以把隐藏类理解成JVM 给框架作者的“内部实现利器”。它把动态类从“公共世界的永久居民”变成“内部世界的临时工具”带来的好处是更干净不污染公共类型系统不容易被随意枚举更可控与 Lookup 权限绑定访问路径更明确更贴用途适合 lambda/代理/访问器这类短命辅助类更利于卸载减少长期占用元空间的概率前提别强引用它最后送你一句我自己用来判断“要不要隐藏类”的反问挺管用这个动态生成的类未来会不会被业务代码当成“类型”来依赖如果会它就不该是隐藏类你需要一个正常类/可见代理如果不会隐藏类很可能是更合适的选择 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者某个被流“治愈”过的 Java 老兵 日期2025-08-25 本文原创转载请注明出处。