最近有小伙伴在开发时,遇到了一个 Spring 占位符,例如 ${token}, 在不同环境下处理不一致的问题,正好对 Spring 对占位符的处理也有一些不清楚的地方,趁此机会,把 Spring 对占位符的处理机制深入了解一下,方便后续排查问题。
经常阅读D瓜哥博客的朋友可能知道,D瓜哥在 Spring 扩展点实践:整合 Apache Dubbo(一): Spring 插件机制简介 中已经介绍了 Spring 的插件机制。在阅读以下内容之前,建议大家先去阅读一下这篇文章中“Spring 插件机制简介”章节的内容,以便于无缝衔接。
在分析的过程中发现, Spring 对占位符有两种截然不同的出来阶段:① XML 配置文件中的占位符;② Java 源代码中 @Value 注解中的占位符。由于内容较多,一篇讲解完有些过长,所以分三篇文章来分别介绍这两种处理过程。
本篇首先来介绍一下对 XML 配置文件中的占位符的处理。
示例代码 在正式开始之前,先来看一下示例代码:
UserRpc.java /** * @author D瓜哥 · https://www.diguage.com * @since 2023-05-02 10:23:49 */ public class UserRpc { @Value("${user.appId}") private String appId; // 这里不使用注解,而是使用 XML 配置 // @Value("${user.token}") private String token; } token.properties user.appId=dummyAppId user.token=dummyToken spring.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- @author D瓜哥 · https://www.diguage.com --> <context:annotation-config/> <bean id="userRpc" class="com.diguage.truman.context.UserRpc"> <!-- XML 配置的占位符实例在此 --> <property name="token" value="${user.token}"/> </bean> <context:property-placeholder location="classpath:token.properties"/> </beans> <bean> 标签处理 在 Spring 启动流程概述 中,已经介绍过,Spring 的启动过程几乎都被封装在 AbstractApplicationContext#refresh 方法中。在 refresh 方法中调用了 refreshBeanFactory 方法;在 refreshBeanFactory 方法执行过程中,调用了 loadBeanDefinitions 方法。而 BeanDefinition 的加载是由 org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions 来完成的。通过 XML 文件配置的 Bean 是由 org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory) (AbstractRefreshableApplicationContext 的子类)处理完成的。处理过程的时序图如下:
在做新需求开发或者相关系统的维护更新时,尤其是涉及到不同系统的接口调用时,在可维护性方面,总感觉有很多地方差强人意。一些零星思考,抛砖引玉,希望引发更多的思考和讨论。总结了大概有如下几条建议:
在接口注释中加入接口文档链接
将调用接口处写上被调用接口文档链接
将接口源代码发布到私服仓库
对于状态值常量,优先在接口参数类或者返回值类中定义
如果使用 Map 对象作为传输载体,要提供 Key 值定义常量
针对 Map 返回值,可以考虑使用将 Map 转化成对象
尽可能简化接口依赖
只传递必要字段,尽量避免大而全的接口
将接口的参数和返回值原始数据打印到日志中
将 RPC 接口的类名及方法打印到日志中
核心思想:以人为本,就近原则,触手可及
下面,D瓜哥对每一条建议做一个详细说明。
1. 在接口注释中加入接口文档链接 在做接口开发时,无论是对自有接口的升级改造,还是针对外部接口的从头接入,都涉及到接口文档。不同之处是,前者的工作重点是书写或者更新接口文档;而后者是根据接口文档开发合适的接入代码。但是,经常遇到的一个麻烦是,找不到接口文档。在组内需要找老同事询问;如果是跨部门,还需要两层甚至三层的进行转接,非常麻烦。
D瓜哥认为,在这种情况下,为了方便大家维护,最好的办法就是将接口文档链接直接放在代码注释中,这样后续维护的人员,直接就可以点击链接直达接口文档,简单方便高效。如果是新建的接口,就可以先创建一个空文档,把链接放在注释中,后续再书写文档内容。如果是维护已有接口,可以在维护时,将缺失的链接加入到注释中,自己方便,也方便其他人进行后续的维护更新。这样,在循序渐进的过程中,逐步就可以把文档链接补充到代码中,方便维护代码,也同步更新文档。
2. 将调用接口处写上被调用接口文档链接 在调用其他系统的接口时,没有接口文档,几乎寸步难行。在第一次接入接口时,绝大多数情况下,都是参考着接口文档做接入工作。但是,目前的情况时,接入时参考文档,参考完就随手把文档给“扔了”。后续如果还需要做进一步升级维护,还需要到处找接口文档;另外,交互的系统难免有一些 Bug,在和其他系统维护人员对接处理 Bug 时,只有接口没有文档,对方可能也需要去找文档链接。无形中,很多时间都浪费在了找文档的过程中。
D瓜哥最近尝试了一个实践,就是在接口调用的地方,把接口文档链接当做注释加入到代码中。这样,无论是后续维护升级,还是沟通协调处理问题,都非常方便。别人问接口是什么,连接口+文档都可以一把复制就搞定。
经过最近一段时间的实践情况来看,这个处理非常方便,是一个非常值得推广的实践。再插一句,也可以像一条建议一样,可以在维护代码时,不断把已接入的接口文档加入到调用接口的地方,循序渐进,方便后续人维护升级。
3. 将接口源代码发布到私服仓库 接口文档链接在注释中,在构建结果中就不复存在了。所以,为了方便接口使用方可以在接口中查询到对应的接口文档,就需要把源码也发布到私服仓库中。
这里只说明一下 Java 的相关处理办法。如果使用 Maven 作为构建工具的话,默认是不会将源代码发布到私服仓库中的。关于如何将源代码发布到,在 升级 Maven 插件:将源码发布到私服仓库 中已经做过相关介绍,这里就不再赘述。
除了将源码发布到私服仓库,另外,还建议编译构建时,保持方法的原始参数命名。这个也可以通过配置 Maven 插件来完成,具体配置见: 升级 Maven 插件:字节码文件包含原始参数名称。
4. 对于状态值常量,优先在接口参数类或者返回值类中定义 在做接口开发时,很多数据都有一个状态值,比如订单状态,再比如接口状态等等。目前的一个情况时,这些状态值大部分书写在文档中,在接入接口时,需要接入方自定义这些状态值。这就有些繁琐了,而且状态定义也不明确,甚至有可能遗漏一些重要的状态值。有些懒省事,直接在代码中硬编码一个魔法值,后续维护的跟还需要根据上下文反推这个值的含义,非常不利于维护。
D瓜哥个人觉得,有两个处理办法:
如果状态值不是很多,优先在接口参数类或者返回值类中定义。
如果状态值很多,可以考虑单独抽取成一个常量类或者枚举类。
这样使用的时候,触手可及。不需要到处去找。
5. 如果使用 Map 对象作为传输载体,要提供 Key 值定义常量 有些系统可能考虑方便增加字段,选择使用 Map 作为数据载体。自己开发的时候很爽,但是给接口接入却非常不友好。接入方从 Map 中获取数据时,要么自己定义 Key 值;要么直接使用魔法值硬编码在代码中。使用前者方案,就需要在各个接入方都需要自定义一套;使用后者,初期是省事了,后来维护的人员就懵逼了。这都无形中增加了很多维护成本。
在 制定组织内 Maven BOM 的一些规范 中,D瓜哥 介绍了一些组织内指定 Maven BOM 的一些规范。根据这些规范,D瓜哥 创建并维护了部门内部的 Maven BOM。今年,要求对部门内的陈旧依赖做一些升级工作。所以,在 关于升级 Spring 等依赖的一些经验 中介绍了一些升级开源依赖的经验;在上一篇文章 升级 Maven 插件 中介绍了升级 Maven 插件的一些注意事项。
D瓜哥一直坚持“机器可以干的事情,就应该交给机器干”。对于依赖管理,Maven Enforcer 插件就可以对依赖做必要的检查,所以,在 使用 Maven Enforcer 插件检查依赖 中,介绍了如何使用 Apache Maven Enforcer 来管理依赖。由于要维护部门内部的 Maven BOM,同时由于版本控的特质,所以,需要时长检查依赖升级情况。原来都是手动检查,需要一个一个去搜索各个依赖,不仅费时费力,而且还低效。最近,Maven 有一个插件可以胜任这个工作,它就是: Versions Maven Plugin。
依赖检查 Versions Maven Plugin 支持两种配置方式:
外置配置文件 maven-version-rules.xml;
内置在 POM 文件中,直接写在插件的 <configuration> 标签中。
第一种方案不方便迁移。还要额外管理一个配置文件。推荐使用第二种方式。另外,直接将这些配置放在 Maven BOM 中,使用继承的方式使用 Maven BOM,那么子项目就自动继承了这些配置。后续也只需要一个地方的配置即可。示例配置如下:
<!-- @author: D瓜哥 · https://www.diguage.com --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-maven-plugin</artifactId> <version>2.15.0</version> <configuration> <ruleSet> <ignoreVersions> <!-- 可以使用 ignoreVersion 配置忽略 SNAPSHOT、alpha、beta 版等 --> <ignoreVersion> <!-- 'exact' (默认) 或 'regex' --> <type>regex</type> <version>(.+-SNAPSHOT|.+-M\d)</version> </ignoreVersion> <ignoreVersion> <type>regex</type> <version>.+-(alpha|beta)</version> </ignoreVersion> </ignoreVersions> </ruleSet> </configuration> </plugin>
D瓜哥在 关于升级 Spring 等依赖的一些经验 中,介绍了一些升级 Spring 等依赖的一些经验。在 升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 的影响 中,分析了升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 异常的影响。在升级中,还遇到一些 Maven 插件相关的问题。这里也分享出来,希望对大家有所帮助。
Properties 文件编码错误 在升级过程中,遇到过 Properties 文件编码错误的问题。可以通过配置对应的编码来解决这个问题。配置如下:
<!-- D瓜哥 · https://www.diguage.com --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.3.0</version> <configuration> <propertiesEncoding>ISO-8859-1</propertiesEncoding> </configuration> </plugin> 参考资料 Apache Maven Resources Plugin – Filtering Properties Files。
使用 Maven Enforcer 插件检查依赖 私以为“机器可以干的事情,就应该交给机器干”。对于依赖管理,Maven Enforcer 插件就可以对依赖做必要的检查。所以,推荐使用 Maven Enforcer 插件来检查低版本及有安全漏洞的依赖。
详细介绍请参考: 使用 Maven Enforcer 插件检查依赖
字节码文件包含原始参数名称 一些对外发布的依赖,建议将原始参数名称编译到构建结果里。可以通过指定构建参数来完成。
<!-- D瓜哥 · https://www.diguage.com --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <!-- https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html --> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <parameters>true</parameters> </configuration> </plugin> 参考资料 Apache Maven Compiler Plugin – Pass Compiler Arguments
解决测试依赖问题 部分项目可能已经使用了 JUnit 5,但是执行测试代码时,可能报错。可以使用如下配置来解决这个问题:
在 关于升级 Spring 等依赖的一些经验 中,分享了一些开源依赖的升级经验。部分小伙伴质疑升级 iBATIS/MyBATIS 会影响对 DuplicateKeyException 异常的处理。这篇文章就从源码分析/代码更新的就角度来分析一下升级相关依赖是否会对 DuplicateKeyException 异常的处理带来实质性的影响。
由于主要的技术栈涉及 MySQL 驱动、iBATIS、MyBATIS、Spring 周边等。所以,本文仅分析涉及的这些依赖。
D瓜哥使用 MySQL: Employees Sample Database 搭建了一个 Spring + MyBATIS + MySQL Connector/J 的测试环境。连续插入两条一样的数据,单步调试,在 com.mysql.jdbc.MysqlIO#sendCommand 方法中,就可以观察到如下异常:
图 1. MySQL Error 1062 从这里可以明显看出,MySQL 驱动返回的异常中, venderCode 编码是 1062。
顺着这个线,往上走,到 org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object) 方法中,可以看到,
图 2. MyBATIS wrap Exception 在这里,会将 SQLException 包装成 PersistenceException,这也是 MyBATIS 对外暴露的统一的异常类。
继续往上走,就到了 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke 方法:
图 3. MyBATIS translateException 在 SqlSessionInterceptor#invoke 方法的异常处理中,将 PersistenceException 异常通过 org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible 方法,将异常转换成 DataAccessException 对象。 DataAccessException 类是 Spring 数据访问的异常类基类。
文章还没写完,提前放出防止出现 404。稍后慢慢更新,敬请期待: 细说编码与字符集 - "地瓜哥"博客网
文章还没写完,提前放出防止出现 404。稍后慢慢更新,敬请期待: 细说编码与字符集 - "地瓜哥"博客网
文章还没写完,提前放出防止出现 404。稍后慢慢更新,敬请期待: 细说编码与字符集 - "地瓜哥"博客网
前段时间要研究 Hessian 编码格式,为了搞清楚 Hessian 对字符串的编码,就顺路查了好多编码和字符集的工作,理清了很多以前模糊的知识点。下面整理一下笔记,也梳理一下自己的思路和理解。
ASCII 码 计算机起源于美国,他们对英语字符与二进制位之间的对应关系做了统一规定,并制定了一套字符编码规则,这套编码规则被称为 American Standard Code for Information Interchange,简称为 ASCII 编码
其实,ASCII 最早起源于电报码。最早的商业应用是贝尔公司的七位电传打字机。后来于 1963 年发布了该标准的第一版。在网络交换中使用的 ASCII 格式是在 1969 年发布的,该格式在 2015 年发展成为互联网标准。点击 RFC 20: ASCII format for network interchange,感受一下 1969 年的古香古色。
ASCII 编码一共定义了128个字符的编码规则,用七位二进制表示(0x00 - 0x7F), 这些字符组成的集合就叫做 ASCII 字符集。完整列表如下:
ASCII 码可以说是现在所有编码的鼻祖。
编码乱战及 Unicode 应运而生 ASCII 编码是为专门英语指定的编码标准,但是却不能编码英语外来词。比如 résumé,其中 é 就不在 ASCII 编码范围内。
随着计算机的发展,各个国家或地区,甚至不同公司都推出了不同的编码标准,比如中国推出了 GB2312、GBK 以及 GB18030;微软推出了 Windows character sets 。
在 Java 虚拟机操作码探秘:常量指令 中对 Java 虚拟机操作码中关于常量操作的指令(操作码)做了初步介绍。估计会有人疑问:文中的“栈”、“栈顶”等是什么?接下来就准备解答这些疑问。
在答疑解惑之前,先来了解一下 Java 编译器对 Java 代码中的代码块是如何处理的?常见的代码块有普通代码块和静态代码块,下面对其做分别介绍。由于涉及到构造函数,所以,先对构造函数做一个介绍。
构造函数 无构造函数 先来看看当没有声明构造函数时,编译结果是什么样的:
/** * 无构造函数示例 * * @author D瓜哥 · https://www.diguage.com */ public class Example { } 编译后,使用 javap -c 查看一下编译结果:
$ javap -c Example Compiled from "Example.java" public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return } 从结果上来看:编译器自动给没有声明构造函数的类,生成了一个无参构造函数,并且在其中调用了父类(这里是 Object)的无参构造函数。这是大家都熟知的基础知识。
有参构造函数 再来看看当有声明参数的构造函数时,编译结果是什么样的:
/** * 有参构造函数示例 * * @author D瓜哥 · https://www.diguage.com */ public class Example { public Example(int i) { } } 编译后,使用 javap -c 查看一下编译结果:
$ javap -c Example Compiled from "Example.java" public class Example { public Example(int); (1) Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return }
在 Java 虚拟机指令(操作码)集 中给出了一个操作码的列表。针对所有的指令,仅仅给出了一个大概介绍,对理解来说可以说毫无助力。为了弥补这个短板,这里也学习 “Hessian 协议解释与实战”系列 那样,来一个详细解释和实战,配合实例来做个深入分析和讲解。这是这个系列的第一篇文章,就以列表中第一部分“常量”指令开始。
从 Java 虚拟机指令(操作码)集 列表上来看,一共 21 个指令;按照处理数据的类型,合并同类项后,剩下有 nop、 aconst_null、 iconst_<i>、 lconst_<l>、 fconst_<f>、 dconst_<d>、 bipush、 sipush、 ldc 和 ldc2_w 等几个指令。下面,按照顺序,对其进行一一讲解。
操作码助记符的首字母一般是有特殊含义的,表示操作码所作用的数据类型: i 代表对 int 类型的数据操作; l 代表 long; s 代表 short; b 代表 byte;c 代表 char;f 代表 float, d 代表 double; a 代表 reference。
尖括号之间的字母指定了指令隐含操作数的数据类型,<n> 代表非负的整数; <i> 代表是 int 类型数据; <l> 代表 long 类型; <f> 代表 float 类型; <d> 代表 double 类型。
另外还需要指出一点:这种指令表示法在整个 Java 虚拟机规范之中都是通用的。
nop 根据 Chapter 6. The Java Virtual Machine Instruction Set:nop 来看,就是“Do nothing”,暂时没有找到使用方法。就不做多介绍,后续看到相关资料,再做补充。
在 关于升级 Spring 等依赖的一些经验 中介绍了 D瓜哥在升级项目依赖时,遇到的一些问题以及一些需要注意的地方。但是,这里还存在一个问题:各个依赖的版本依然散落在各个项目中;升级依赖,需要在所有项目中,把所有相关项目的依赖都巴拉一下,费时费力。解决这个问题的一个比较好的办法是制定一个组织内部的 Maven BOM,集中管理相关依赖的版本。这样升级的时候,还需要修改 BOM 的版本号即可。
Maven BOM 介绍 BOM(Bill of Materials)是由 Maven 提供的功能,它通过定义一整套相互兼容的 jar 包版本集合,使用时只需要依赖该BOM文件,即可放心的使用需要的依赖 jar 包,且无需再指定版本号。
一些基本原则 Spring & Spring Boot 是 Java 生态中,全世界广泛使用的开发框架,在各种场景中都经受过考验。所以,Spring & Spring Boot 选择的 Jar 在稳定性和兼容性方面都有保证。另外,Spring Boot 本身就集成了非常非常多的依赖,并为此创建了一个网页 Spring Boot Dependency versions 来说明它集成的依赖及版本。故而,可以选择以 Spring Boot 为底本,来制作自己的 BOM。
如果不需要 Spring 相关依赖,可以将 Spring 相关依赖删除掉,然后在其之上增加组织内部依赖而创建自己的 BOM。
如果需要 Spring 相关依赖,那么直接继承
在稳定性方面,经过更多人检验的版本,则稳定性更有保障。所以,选择最近两年下载次数比较多的版本。
更新的版本,更容易获得技术升级带来的红利。所以,在可能的情况下,优先选择高版本。
优先考虑目标 JDK 的支持情况。例如,一些依赖的高版本或低版本不支持 Java 8,但是 Java 8 是生产环境部署的主要版本,那么太高的版本和低版本都不适合。
外部 Jar 包选择标准 尽量将外部中间件统一到同一种依赖的同一个版本上。例如:数据库连接池全部使用 HikariCP;JSON 处理统一使用 Jackson。
选择最近两年发布的版本中,下载次数最多的版本为准。如果有发布的小版本升级,则在该版本基础上,该版本的最新修订版。例如,1.2.3 是最近两年下载最多的版本,但是 1.2.4 已经发布,则优先选择使用 1.2.4。
如果有两个大版本,高版本符合条件的情况下,优先选择高版本。低版本大概率是先淘汰的,高版本相对来说维护时间更长,另外高版本的代码优化得更佳。例如,Ehcache 的选择。
如果传递依赖造成依赖 Jar 包版本冲突,则尽可能选择高版本的 Jar。
持续演进的项目的依赖优先级更高;相反,临近淘汰的项目优先级降低,甚至不予考虑。
两年以上未更新的依赖,在 API 兼容的情况下,直接升级到最新版。
没有显示使用而是间接引入的依赖,不再单独声明,由直接依赖来引入。如果需要解决冲突,则按照上面的原则来处理。
最近在研究 Java 虚拟机字节码。在 《Java虚拟机规范》 看到一个整理完整的 Java 虚拟机指令集(也叫操作码)列表。转载过来,方便查阅。
关于 Java 虚拟机指令(操作码),准备写一个“探秘”系列:
Java 虚拟机操作码探秘:常量指令 — 重点介绍一下关于“常量”指令。
分类 操作码 助记符 指令含义 常量
0
0x00
nop
什么都不做
1
0x01
aconst_null
将 null 推送至栈顶
2
0x02
iconst_m1
将 int 类型 -1 推送至栈顶
3
0x03
iconst_0
将 int 类型 0 推送至栈顶
4
0x04
iconst_1
将 int 类型 1 推送至栈顶
5
0x05
iconst_2
将 int 类型 2 推送至栈顶
6
0x06
iconst_3
将 int 类型 3 推送至栈顶
7
0x07
iconst_4
将 int 类型 4 推送至栈顶
8
0x08
iconst_5
将 int 类型 5 推送至栈顶
9
0x09
lconst_0
将 long 类型 0 推送至栈顶
10
0x0a
lconst_1
将 long 类型 1 推送至栈顶
11
0x0b
fconst_0
将 float 类型 0 推送至栈顶
12
0x0c
fconst_1
将 float 类型 1 推送至栈顶
13
0x0d
fconst_2
将 float 类型 2 推送至栈顶
14
0x0e
dconst_0
将 double 类型 0 推送至栈顶
15
0x0f
dconst_1
将 double 类型 1 推送至栈顶
16
0x10
bipush
将单字节的常量值(-128 ~ 127)推送至栈顶
17
0x11
sipush
将一个短整类型常量值(-32,768 ~ 32,767)推送栈顶
18
0x12
ldc
将 int、 float 或 String 类型常量值从常量池中推送至栈顶
19
0x13
ldc_w
将int、 float 或 String 类型常量值从常量池中推送栈顶(宽索引)
20
0x14
ldc2_w
将 long 或 double 类型常量值从常量池中推送至栈(宽索引)
加载
21
0x15
iload
将指定的 int 类型本地变量推送至栈顶
22
0x16
lload
将指定的 long 类型本地变量推送至栈顶
23
0x17
fload
将指定的 float 类型本地变量推送至栈顶
24
0x18
dload
将指定的 double 类型本地变量推送至栈顶
25
0x19
aload
将指定的引用类型本地变量推送至栈顶
26
0x1a
iload_0
将第 1 个 int 类型本地变量推送至栈顶
27
0x1b
iload_1
将第 2 个 int 类型本地变量推送至栈顶
28
0x1c
iload_2
将第 3 个 int 类型本地变量推送至栈顶
29
0x1d
iload_3
将第 4 个 int 类型本地变量推送至栈顶
30
0x1e
lload_0
将第 1 个 long 类型本地变量推送至栈顶
31
0x1f
lload_1
将第 2 个 long 类型本地变量推送至栈顶
32
0x20
lload_2
将第 3 个 long 类型本地变量推送至栈顶
33
0x21
lload_3
将第 4 个 long 类型本地变量推送至栈顶
34
0x22
fload_0
将第 1 个 float 类型本地变量推送至栈顶
35
0x23
fload_1
将第 2 个 float 类型本地变量推送至栈顶
36
0x24
fload_2
将第 3 个 float 类型本地变量推送至栈顶
37
0x25
fload_3
将第 4 个 float 类型本地变量推送至栈顶
38
0x26
dload_0
将第 1 个 double 类型本地变量推送至栈顶
39
0x27
dload_1
将第 2 个 double 类型本地变量推送至栈顶
40
0x28
dload_2
将第 3 个 double 类型本地变量推送至栈顶
41
0x29
dload_3
将第 4 个 double 类型本地变量推送至栈顶
42
0x2a
aload_0
将第 1 个引用类型本地变量推送至栈顶
43
0x2b
aload_1
将第 2 个引用类型本地变量推送至栈顶
44
0x2c
aload_2
将第 3 个引用类型本地变量推送至栈顶
45
0x2d
aload_3
将第 4 个引用类型本地变量推送至栈顶
46
0x2e
iaload
将 int 类型数组的指定元素推送至栈顶
47
0x2f
laload
将 long 类型数组的指定元素推送至栈顶
48
0x30
faload
将 float 类型数组的指定元素推送至栈顶
49
0x31
daload
将 double 类型数组的指定元素推送至栈顶
50
0x32
aaload
将引用类型数组的指定元素推送至栈顶
51
0x33
baload
将 boolean 或 byte 类型数组的指定元素推送至栈顶
52
0x34
caload
将 char 类型数组的指定元素推送至栈顶
53
0x35
saload
将 short 类型数组的指定元素推送至栈顶
存储
54
0x36
istore
将栈顶 int 类型数值存入指定本地变量
55
0x37
lstore
将栈顶 long 类型数值存入指定本地变量
56
0x38
fstore
将栈顶 float 类型数值存入指定本地变量
57
0x39
dstore
将栈顶 double 类型数值存入指定本地变量
58
0x3a
astore
将栈顶引用类型数值存入指定本地变量
59
0x3b
istore_0
将栈顶 int 类型数值存入第 1 个本地变量
60
0x3c
istore_1
将栈顶 int 类型数值存入第 2 个本地变量
61
0x3d
istore_2
将栈顶 int 类型数值存入第 3 个本地变量
62
0x3e
istore_3
将栈顶 int 类型数值存入第 4 个本地变量
63
0x3f
lstore_0
将栈顶 long 类型数值存入第 1 个本地变量
64
0x40
lstore_1
将栈顶 long 类型数值存入第 2 个本地变量
65
0x41
lstore_2
将栈顶 long 类型数值存入第 3 个本地变量
66
0x42
lstore_3
将栈顶 long 类型数值存入第 4 个本地变量
67
0x43
fstore_0
将栈顶 float 类型数值存入第 1 个本地变量
68
0x44
fstore_1
将栈顶 float 类型数值存入第 2 个本地变量
69
0x45
fstore_2
将栈顶 float 类型数值存入第 3 个本地变量
70
0x46
fstore_3
将栈顶 float 类型数值存入第 4 个本地变量
71
0x47
dstore_0
将栈顶 double 类型数值存入第 1 个本地变量
72
0x48
dstore_1
将栈顶 double 类型数值存入第 2 个本地变量
73
0x49
dstore_2
将栈顶 double 类型数值存入第 3 个本地变量
74
0x4a
dstore_3
将栈顶 double 类型数值存入第 4 个本地变量
75
0x4b
astore_0
将栈顶引用类型数值存入第 1 个本地变量
76
0x4c
astore_1
将栈顶引用类型数值存入第 2 个本地变量
77
0x4d
astore_2
将栈顶引用类型数值存入第 3 个本地变量
78
0x4e
astore_3
将栈顶引用类型数值存入第 4 个本地变量
79
0x4f
iastore
将栈顶 int 类型数值存入指定数组的指定索引位置
80
0x50
lastore
将栈顶 long 类型数值存入指定数组的指定索引位置
81
0x51
fastore
将栈顶 float 类型数值存入指定数组的指定索引位置
82
0x52
dastore
将栈顶 double 类型数值存入指定数组的指定索引位置
83
0x53
aastore
将栈顶引用类型数值存入指定数组的指定索引位置
84
0x54
bastore
将栈顶 boolean 或 byte 类型数值存入指定数组的指定索引位置
85
0x55
castore
将栈顶 char 类型数值存入指定数组的指定索引位置
86
0x56
sastore
将栈顶 short 类型数值存入指定数组的指定索引位置
栈
87
0x57
pop
将栈顶数值弹出(数值不能是 long 或 double 类型的)
88
0x58
pop2
将栈顶的一个 long 或 double 类型的数值或两个其他类型的数值弹出
89
0x59
dup
复制栈顶数值并将复制值压入栈顶
90
0x5a
dup_x1
复制栈顶值并将其插入栈顶那两个值的下面
91
0x5b
dup_x2
复制栈顶值并将其插入栈顶那两个或三个值的下面
92
0x5c
dup2
复制栈顶的一个 long 或 double 类型的值,或两个其他类型的值,并将其压入栈顶
93
0x5d
dup2_x1
复制栈顶的一个或两个值,并将其插入栈顶那两个或三个值的下面
94
0x5e
dup2_x2
复制栈顶的一个或两个值,并将其插入栈顶那两个、三个或四个值的下面
95
0x5f
swap
将栈顶的两个数值互换(数值不能是 long 或 double 类型的)
数学
96
0x60
iadd
将栈顶两 int 类型数值相加并将结果压入栈顶
97
0x61
ladd
将栈顶两 1ong 类型数值相加并将结果压入栈顶
98
0x62
fadd
将栈顶两 float 类型数值相加并将结果压入栈顶
99
0x63
dadd
将栈顶两 double 类型数值相加并将结果压入栈顶
100
0x64
isub
将栈顶两 int 类型数值相减并将结果压入栈顶
101
0x65
lsub
将栈顶两 long 类型数值相减并将结果压入栈顶
102
0x66
fsub
将栈顶两 float 类型数值相减并将结果压入栈顶
103
0x67
dsub
将栈顶两 double 类型数值相减并将结果压入栈顶
104
0x68
imul
将栈顶两 int 类型数值相乘并将结果压入栈顶
105
0x69
lmul
将栈顶两 long 类型数值相乘并将结果压入栈顶
106
0x6a
fmul
将栈顶两 float 类型数值相乘并将结果压入栈顶
107
0x6b
dmul
将栈顶两 double 类型数值相乘并将结果压入栈顶
108
0x6с
idiv
将栈顶两 int 类型数值相除并将结果压入栈顶
109
0x6d
ldiv
将栈顶两 long 类型数值相除并将结果压入栈顶
110
0x6e
fdiv
将栈顶两 float 类型数值相除并将结果压入栈顶
111
0x6f
ddiv
将栈顶两 double 类型数值相除并将结果压入栈顶
112
0x70
irem
将栈顶两 int 类型数值作取模运算并将结果压入栈顶
113
0x71
lrem
将栈顶两 long 类型数值作取模运算并将结果压入栈顶
114
0x72
frem
将栈顶两 float 类型数值作取模运算并将结果压入栈顶
115
0x73
drem
将栈顶两 double 类型数值作取模运算并将结果压入栈顶
116
0x74
ineg
将栈顶 int 类型数值取负并将结果压入栈顶
117
0x75
lneg
将栈顶 long 类型数值取负并将结果压入栈顶
118
0x76
fneg
将栈顶 float 类型数值取负并将结果压入栈顶
119
0x77
dneg
将栈顶 double 类型数值取负并将结果压入栈顶
120
0x78
ishl
将 int 类型数值左移位指定位数并将结果压入栈顶
121
0x79
lshl
将 long 类型数值左移位指定位数并将结果压入栈顶
122
0x7a
ishr
将 int 类型数值(有符号)右移位指定位数并将结果压入栈顶
123
0x7b
lshr
将 long 类型数值(有符号)右移位指定位数并将结果压入栈顶
124
0x7c
iushr
将 int 类型数值(无符号)右移位指定位数并将结果压入栈顶
125
0x7d
lushr
将 long 类型数值(无符号)右移位指定位数并将结果压入栈顶
126
0x7e
iand
将栈顶两 int 类型数值作“按位与”并将结果压入栈顶
127
0x7f
land
将栈顶两 long 类型数值作“按位与”并将结果压入栈顶
128
0x80
ior
将栈顶两 int 类型数值作“按位或”并将结果压入栈顶
129
0x81
lor
将栈顶两 long 类型数值作“按位或”并将结果压入栈顶
130
0x82
ixor
将栈顶两 int 类型数值作“按位异或”并将结果压入栈顶
131
0x83
lxor
将栈顶两 long 类型数值作“按位异或”并将结果压入栈顶
132
0x84
iinc
将指定 int 类型变量增加指定值(i++,i--,i += 2)
转换
133
0x85
i2l
将栈顶 int 类型数值强制转换成 long 类型数值并将结果压入栈顶
134
0x86
i2f
将栈顶 int 类型数值强制转换成 float 类型数值并将结果压入栈顶
135
0x87
i2d
将栈顶 int 类型数值强制转换成 double 类型数值并将结果压入栈顶
136
0x88
l2i
将栈顶 long 类型数值强制转换成 int 类型数值并将结果压入栈顶
137
0x89
l2f
将栈顶 long 类型数值强制转换成 float 类型数值并将结果压入栈顶
138
0x8a
l2d
将栈顶 long 类型数值强制转换成 double 类型数值并将结果压入栈顶
139
0x8b
f2i
将栈顶 float 类型数值强制转换成 int 类型数值并将结果压入栈顶
140
0x8c
f2l
将栈顶 float 类型数值强制转换成 long 类型数值并将结果压入栈顶
141
0x8d
f2d
将栈顶 float 类型数值强制转换成 double 类型数值并将结果压入栈顶
142
0x8e
d2i
将栈顶 double 类型数值强制转换成 int 类型数值并将结果压入栈顶
143
0x8f
d2l
将栈顶 double 类型数值强制转换成 long 类型数值并将结果压入栈顶
144
0x90
d2f
将栈顶 double 类型数值强制转换成 float 类型数值并将结果压入栈顶
145
0x91
i2b
将栈顶 int 类型数值强制转换成 byte 类型数值并将结果压入栈顶
146
0x92
i2c
将栈顶 int 类型数值强制转换成 char 类型数值并将结果压入栈顶
147
0x93
i2s
将栈顶 int 类型数值强制转换成 short 类型数值并将结果压入栈顶
比较
148
0x94
lcmp
比较栈顶两 long 类型数值大小,并将结果(1,0,-1)压入栈顶
149
0x95
fcmpl
比较栈顶两 float 类型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将 -1 压入栈顶
150
0x96
fcmpg
比较栈顶两 float 类型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将1压入栈顶
151
0x97
dcmpl
比较栈顶两 double 类型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将-1压入栈顶
152
0x98
dcmpg
比较栈顶两 double 类型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将1压入栈顶
153
0x99
ifeq
当栈顶 int 类型数值等于 0 时跳转
154
0x9a
ifne
当栈顶 int 类型数值不等于 0 时跳转
155
0x9b
iflt
当栈顶 int 类型数值小于 0 时跳转
156
0x9c
ifge
当栈顶 int 类型数值大于等于 0 时跳转
157
0x9d
ifgt
当栈顶 int 类型数值大于 0 时跳转
158
0x9e
ifle
当栈顶 int 类型数值小于等于 0 时跳转
159
0x9f
if_icmpeq
比较栈顶两 int 类型数值大小,当前者等于后者时跳转
160
0xa0
if_icmpne
比较栈顶两 int 类型数值大小,当前者不等于后者时跳转
161
0xa1
if_icmplt
比较栈顶两 int 类型数值大小,当前者小于后者时跳转
162
0xa2
if_icmpge
比较栈顶两 int 类型数值大小,当前者大于等于后者时跳转
163
0xa3
if_icmpgt
比较栈顶两 int 类型数值大小,当前者大于后者时跳转
164
0xa4
if_icmple
比较栈顶两 int 类型数值大小,当前者小于等于后者时跳转
165
0xa5
if_acmpeq
比较栈顶两引用类型数值,当结果相等时跳转
166
0xa6
ifacmpne
比较栈顶两引用类型数值,当结果不相等时跳转
控制
167
0xa7
goto
无条件跳转
168
0xa8
jsr
跳转至指定 16 位 offset 位置,并将 jsr 下一条指令地址压入栈顶
169
0xa9
ret
返回至由指定的局部变量所给出的指令位置(一般与 jsr、jsr_w 联合使用)
170
0xaa
tableswitch
用于 switch 条件跳转,case 值连续(变长指令)
171
0xab
lookupswitch
用于 switch 条件跳转,case 值不连续(变长指令)
172
0xac
ireturn
从当前方法返回 int
173
Oxad
lreturn
从当前方法返回 long
174
0xae
freturn
从当前方法返回 float
175
0xaf
dreturn
从当前方法返回 double
176
0xb0
areturn
从当前方法返回对象引用
177
0xb1
return
从当前方法返回void
引用
178
0xb2
getstatic
获取指定类的静态字段,并将其值压入栈顶
179
0xb3
putstatic
为指定类的静态字段赋值
180
0xb4
getfield
获取指定类的实例字段,并将其值压入栈顶
181
0xb5
putfield
为指定类的实例字段赋值
182
0xb6
invokevirtual
调用实例方法
183
0xb7
invokespecial
调用父类方法、实例初始化方法、私有方法
184
0xb8
invokestatic
调用静态方法
185
0xb9
invokeinterface
调用接口方法
186
0xba
invokedynamic
调用动态链接方法
187
0xbb
new
创建一个对象,并将其引用值压入栈顶
188
0xbc
newarray
创建一个指定原始类型(如int、float 、char等)的数组,并将其引用值压入栈顶
189
0xbd
anewarray
创建一个引用型(如类、接口、数组)的数组,并将其引用值压入栈顶
190
0xbe
arraylength
获得数组的长度值并压入栈顶
191
0xbf
athrow
将栈顶的异常抛出
192
0xcO
checkcast
检验类型转换,检验未通过将抛出 ClassCastException
193
0xc1
instanceof
检验对象是否是指定类的实例。如果是,就将 1 压入栈顶,否则将 0 压入栈顶
194
0xc2
monitorenter
获得对象的锁,用于实现同步块
195
0xc3
monitorexit
释放对象的锁,用于实现同步块
扩展
196
0xc4
wide
扩展本地变量索引的宽度
197
0xс5
multianewarray
创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
198
0xc6
ifnull
为nu11时跳转
199
0xc7
ifnonnull
不为nu11时跳转
200
0xc8
goto_w
无条件跳转(宽索引)
201
0xc9
jsr_w
跳转至指定 32 位 offset 位置,并将 jsr_w 下一条指令地址压入栈顶
保留指令
202
Оxca
breakpoint
调试时的断点标记
254
Oxfe
impdep1
为特定软件面预留的语言后门
255
0xff
impdep2
为特定硬件面预留的语言后门
参考资料 Chapter 6. The Java Virtual Machine Instruction Set
Java bytecode
List of Java bytecode instructions