ASM 自定义属性(Attribute)机制实战:用 `Attribute` 子类扩展类文件格式,CodeGuide 附录 A.3 深度解析
文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载本篇文章基于 CodeGuide 仓库中 ASM 文档的 A.3 属性 一章系统讲解 JVM 类文件格式中属性Attribute这一可扩展机制的底层原理以及 ASM 中Attribute类的完整用法。读完本文你将掌握类、字段、方法三级属性在 ASM 事件模型中的访问入口ClassReader对标准属性与非标准属性的默认处理策略以及如何通过编写Attribute子类实现自定义属性的解码与重新编码并正确注册到ClassReader.accept中。属性机制类文件格式留给扩展的后门在 2.1 结构 一章中可以看到一个已编译类的整体结构由多个部分组成类描述修饰符、名字、超类、接口、常量池、源文件名、封装的类引用、注释、内部类以及类中声明的每个字段和方法各自对应的部分。其中类、字段、方法这三层结构上都留有一个带星号的属性*槽位星号表示零个或多个已编译类的整体结构*表示零个或多个 ├── 修饰符、名字、超类、接口 ├── 常量池数值、字符串和类型常量 ├── 源文件名可选 ├── 封装的类引用 ├── 注释* ├── 属性* ├── 内部类* ├── 字段* 修饰符、名字、类型 │ 注释* │ 属性* └── 方法* 修饰符、名字、返回类型与参数类型 注释* 属性* 编译后的代码属性机制正是 JVM 规范为类文件格式留出的可扩展后门可以将任意属性关联到类、字段和方法。在引入新特性时这种可扩展机制对于扩展类文件格式非常有用。例如它已经被用于扩展这一格式以支持注释Annotations、泛型Signature 属性、栈映射帧StackMapTable等 JVM 新特性——这些特性都是以标准属性的形式注入类文件的。这一机制还可由用户使用而不只是由 JVM 规范制定者使用。不过自从在 Java 5 中引入注释之后注释的使用就比属性容易得多——注释有语言级语法支持可以被编译器、工具链和反射 API 统一处理。因此如果你真的需要使用自己的属性或者必须管理由别人定义的非标准属性就需要在 ASM 中用Attribute类来完成。默认行为ClassReader 如何对待未知属性默认情况下ClassReader类会为它找到的每个标准属性创建一个Attribute实例并以这个实例为参数调用visitAttribute方法。至于调用的是ClassVisitor、FieldVisitor还是MethodVisitor的该方法则取决于属性出现的上下文——也就是说ASM 的事件模型在类、字段、方法三个层级上分别暴露了属性访问入口详见 2.2 接口和组件 与 3.2 接口和组件 中的ClassVisitor.visitAttribute、FieldVisitor.visitAttribute、MethodVisitor.visitAttribute方法。对于非标准未知属性Attribute实例中保存的是这个属性的原始内容其形式为一个私有的字节数组。ClassWriter在访问这种未知属性时会将这个原始字节数组原样复制到它构建的新类中。默认复制行为为何依赖 2.2.4 节的优化这里有一个非常关键的隐含前提这一默认行为只有在使用 2.2.4 节 介绍的优化时才是安全的。所谓 2.2.4 节的优化指的是ClassReader与ClassWriter互相持有对方引用时的常量池复制优化ClassReader cr new ClassReader(b1); ClassWriter cw new ClassWriter(cr, 0); ChangeVersionAdapter ca new ChangeVersionAdapter(cw); cr.accept(ca, 0); byte[] b2 cw.toByteArray();当ClassReader检测到它返回的MethodVisitor来自一个ClassWriter时意味着该方法的内容不会被转换ClassReader就不会去分析方法内容、生成相应事件而是直接把ClassWriter中表示该方法的字节数组复制过去。原始属性内容本质上是一段与常量池索引交织的字节序列——属性内部往往通过索引引用常量池中的 UTF8 字符串等常量。如果不使用复制常量池优化ClassWriter会构建一个全新的常量池原来属性字节中记录的常量池索引就会失效原内容可能会与类编写器创建的新常量池不一致从而导致类文件被损坏。而使用该优化时原类中定义的所有常量都会被复制到转换后的类中属性字节内的索引得以原样保留。所以这一优化除了提高性能外还是保证未知属性原样复制安全性的另一重要原因。关于该优化的性能收益A.5 性能 一章给出的基准数据显示复制常量池优化可提速 15%20%。默认行为的局限性内容不透明默认情况下非标准属性会以它在已转换类中的形式被复制它的内容对 ASM 和用户来说是完全不透明的——ASM 不做任何解析你也无法读取或修改其中的业务信息。如果只是路过某个类、不希望破坏其自定义属性这个默认行为是够用的但如果你需要访问属性内容就必须主动介入定义一个Attribute子类实现对该属性原始内容的解码read与重新编码write在ClassReader.accept方法中传送这个类的一个原型实例使ClassReader可以解码这一类型的属性。实战编写 CommentAttribute 自定义属性下面用文档中的完整示例来演示。假设我们要实现一个设想的注释特性它的原始内容是一个short值引用存储在常量池中的一个 UTF8 字符串。对应的Attribute子类如下class CommentAttribute extends Attribute { private String comment; public CommentAttribute(final String comment) { super(Comment); this.comment comment; } public String getComment() { return comment; } Override public boolean isUnknown() { return false; } Override protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) { return new CommentAttribute(cr.readUTF8(off, buf)); } Override protected ByteVector write(ClassWriter cw, byte[] code, int len, int maxStack, int maxLocals) { return new ByteVector().putShort(cw.newUTF8(comment)); } }逐段剖析这个类构造器与属性名super(Comment)将属性名固定为Comment。属性名在类文件格式中是一个常量池索引指向 UTF8 字符串JVM 用它来识别属性类型自定义属性名应避免与已登记的标准属性名冲突。isUnknown()返回false这是向 ASM 表明本类不是未知属性而是可被 ASM 识别、可解码的自定义属性类型。这也是继承Attribute时必须重写的方法。read方法对这一类型的属性的原始内容进行解码。参数中的off是属性值在ClassReader所读字节数组中的偏移量buf是用于读取 UTF8 字符串的临时字符缓冲。这里调用cr.readUTF8(off, buf)直接从偏移处读出 UTF8 字符串——这也印证了原始内容中short值引用常量池 UTF8 字符串的结构设计。write方法执行逆操作即重新编码。cw.newUTF8(comment)在ClassWriter的常量池中创建或复用一个 UTF8 常量并返回其索引new ByteVector().putShort(...)将该索引以short形式写入属性字节数组。注意read方法必须返回一个新的属性实例。ASM 会为每个被识别的该类型属性分别调用一次read并收集返回的新实例因此不能在read中返回this或复用已有实例。注册原型实例仅仅定义子类还不够。ClassReader需要知道遇到名为Comment的属性时应该用哪个类去解码。为此必须在调用accept时传入该类型的一个原型实例ClassReader cr ...; ClassVisitor cv ...; cr.accept(cv, new Attribute[] { new CommentAttribute() }, 0);accept的第二个参数是一个Attribute数组里面放的是原型实例原型中的字段值无关紧要解码时会被read生成的新实例覆盖。accept的第三个参数是int类型的解析标志此处为 0表示默认解析行为。完成注册后类文件中的每一个Comment属性都将被识别并为它们中的每一个都创建一个CommentAttribute实例——这些实例携带解码后的comment值可以在你的ClassVisitor的visitAttribute回调中直接使用。而其他未被识别的属性仍会用普通的Attribute实例表示继续走原样复制的默认路径。属性与注释什么时候该用自定义属性JVM 类文件中的注释机制 可以看作属性的进化形态类、字段、方法和方法参数注释只要保留策略不是RetentionPolicy.SOURCE就会被存储在编译后的类中ASM为其提供了独立的AnnotationVisitor访问器体系visitAnnotation、visitEnum、visitArray等ASMifier 甚至可以直接生成构造注释类的 ASM 代码。两者对比实践中的选择标准可以归纳为能用注释解决的问题优先用注释注释有源代码语法、编译器检查、javac与反射 API 的统一支持AnnotationVisitor提供类型安全的嵌套结构访问开发成本远低于属性。必须处理非标准属性时才使用Attribute典型场景包括——读取第三方库生成的、带有私有属性的类文件实现 JVM 规范之外、未被 ASM 内置识别的标准属性如 ASM 自身尚未支持的较新规范属性或者你确实需要向类文件中写入不透明的二进制附加信息。三处访问入口类、字段、方法的 visitAttribute由于属性可以挂在类、字段、方法三个层级上ASM 在三个访问器抽象类中分别定义了visitAttribute(Attribute attr)方法参见 ClassVisitor 与 MethodVisitor 的源码结构访问器方法触发时机ClassVisitorvisitAttribute(Attribute attr)在visit之后、visitInnerClass/visitField/visitMethod之前FieldVisitorvisitAttribute(Attribute attr)在字段的visitAnnotation之后、visitEnd之前MethodVisitorvisitAttribute(Attribute attr)在方法的注释与参数注释之后、visitCode之前事件模型的调用顺序约束见 2.2 接口和组件visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd方法层级的顺序约束见 3.2 接口和组件visitAnnotationDefault? ( visitAnnotation | visitParameterAnnotation | visitAttribute )* ( visitCode ... visitMaxs )? visitEnd因此在编写类适配器时若要读取自定义属性并据此调整转换逻辑需要重写对应层级的visitAttribute若要新增属性则需要遵循上述调用顺序将visitAttribute调用插入到正确的时间点。使用工具验证自定义属性在开发Attribute子类时2.3 工具 一章提供的调试工具同样适用TraceClassVisitor生成所访问类的文本表示。用它替代或包裹ClassWriter可以直观地看到自定义属性是否被正确解析、read/write往返后内容是否一致。CheckClassAdapter验证对visitAttribute等方法的调用顺序与参数是否合法在属性接入事件链出现顺序错误时提前抛出异常避免产出被 JVM 验证器拒绝的类文件。ASMifier如果你想知道如何用 ASM 生成某个带属性的类先写 Java 源码、用javac编译再用 ASMifier 访问编译产物即可得到对应的 ASM 生成代码其中就包括visitAttribute的调用写法。小结围绕 A.3 属性本文梳理了完整的技术闭环属性是 JVM 类文件格式官方的可扩展机制可挂在类、字段、方法三层ClassReader默认把未知属性封装为不透明的Attribute实例通过visitAttribute交给访问链ClassWriter原样复制其原始字节原样复制只有在启用 2.2.4 节 的常量池复制优化时才是安全的否则新常量池会让属性内索引失效、损坏类文件要读写自定义属性需定义Attribute子类并重写read/write/isUnknown再通过ClassReader.accept(cv, new Attribute[]{...}, 0)注册原型实例Java 5 注释机制出现后能用注释解决的场景优先用注释自定义属性只用于处理非标准属性或二进制附加信息的场景。CodeGuide 仓库中 asm-document 目录还包含完整的属性、注释、方法、后向兼容等章节如 A.4 规则、4.2 注释、A.5 性能结合 ASM 字节码编程实战系列 中的 JavaAgent 与字节码插桩案例Javassist 篇、Byte-buddy 篇、ASM 篇阅读可以形成从原理到落地的完整知识链路。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐深度解析 NumPy 文档构建autosummary attribute 模板与类属性文档页生成机制深度解析 NumPy 文档构建autosummary attribute 模板与类属性文档页生成机制 导读 Sphinx 的 autosummary 扩展是科学计算数据分析Cherry Studio 完整教程3 步接入 300 AI 模型桌面端一站式搞定Cherry Studio 完整教程3 步接入 300 AI 模型桌面端一站式搞定 你是不是也遇到过这种场面OpenAI 的 key 躺在浏览器书签里AI 应用大模型桌面应用本地部署RAGPHPStan 错误标识符解析attribute.trait——Trait 不能用作属性Attribute类PHPStan 错误标识符解析attribute.trait——Trait 不能用作属性Attribute类 本文以 PHPStan 官方错误标识符文档开发工具代码质量静态分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Lighthouse挂载COS实现静态资源零侵入托管

Lighthouse挂载COS实现静态资源零侵入托管

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 6:26:26 阅读更多 →
RenderDoc Python API 捕获指南:进程执行、注入与目标控制(Capturing)全解析

RenderDoc Python API 捕获指南:进程执行、注入与目标控制(Capturing)全解析

开发工具调试器图形学GPU 【免费下载链接】renderdoc RenderDoc is a stand-alone graphics debugging tool. 项目地址: https://gitcode.com/gh_mirrors/re/renderdoc 点击查看 免费下载 本指南围绕 RenderDoc Python 模块 renderdoc 中与"捕获(C…

2026/9/24 6:25:26 阅读更多 →
从数据源到产品落地,这些媒体源头API服务商值得信赖

从数据源到产品落地,这些媒体源头API服务商值得信赖

把上游数据接进自家产品之后,最先遇到的问题往往不是数据不够,而是数据用不出结果。 接口能返回内容,报表也能生成,可当产品要给出一个“这次投放值不值”的结论时,团队会发现中间还缺一层转换:接口给的是事…

2026/9/24 6:25:26 阅读更多 →

最新新闻

保安员资格考试教材PDF高效备考指南:四周计划与避坑要点

保安员资格考试教材PDF高效备考指南:四周计划与避坑要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 7:48:14 阅读更多 →
S32K3多核MCU开发入门:启动流程、核间通信与EB配置详解

S32K3多核MCU开发入门:启动流程、核间通信与EB配置详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 7:48:14 阅读更多 →
批量视频去硬字幕工具怎么选?工具与批处理专业服务商对比

批量视频去硬字幕工具怎么选?工具与批处理专业服务商对比

每天要处理几十条视频时,逐条设置和检查会成为实际工作。比较批量视频去字幕方案,不能只看一条样片能否把字幕去掉,还要把整批素材的提交、区域设置、结果复核、问题修改、费用和最终文件一起看。网页上完成一次处理很方便,但持续…

2026/9/24 7:48:14 阅读更多 →
论文AI率太高怎么降?有效果兜底的AI智能降重工具推荐,降AI率没达标包退全款

论文AI率太高怎么降?有效果兜底的AI智能降重工具推荐,降AI率没达标包退全款

最近毕业季身边不少同学在论文查重上栽了跟头,尤其是AIGC检测部分更是让人头疼。根据教育部2025年发布的《高等学位论文质量监测年报》显示,全国本科毕业论文中疑似存在AI痕迹的比例高达29.7%,而硕士论文更是攀升至34.2%。随着政策不断收紧&a…

2026/9/24 7:48:14 阅读更多 →
深入解析 Airbyte Typeform Source Connector 的独特行为:单次使用刷新令牌与增量同步设计

深入解析 Airbyte Typeform Source Connector 的独特行为:单次使用刷新令牌与增量同步设计

数据工程数据集成ETL后端大数据 【免费下载链接】airbyte Open-source data movement for ELT pipelines and AI agents — from APIs, databases & files to warehouses, lakes, and AI applications. Both self-hosted and Cloud. 项目地址: https://gitcode.…

2026/9/24 7:48:14 阅读更多 →
ESP32脑电波控制空调:从BCI信号采集到红外发射的完整实战

ESP32脑电波控制空调:从BCI信号采集到红外发射的完整实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 7:47:13 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →