Spring 对占位符的处理(一):XML 中的 Bean

Spring 对占位符的处理(一):XML 中的 Bean

D瓜哥
最近有小伙伴在开发时,遇到了一个 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 的子类)处理完成的。处理过程的时序图如下:
关于接口可维护性的一些建议

关于接口可维护性的一些建议

D瓜哥
在做新需求开发或者相关系统的维护更新时,尤其是涉及到不同系统的接口调用时,在可维护性方面,总感觉有很多地方差强人意。一些零星思考,抛砖引玉,希望引发更多的思考和讨论。总结了大概有如下几条建议: 在接口注释中加入接口文档链接 将调用接口处写上被调用接口文档链接 将接口源代码发布到私服仓库 对于状态值常量,优先在接口参数类或者返回值类中定义 如果使用 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 值;要么直接使用魔法值硬编码在代码中。使用前者方案,就需要在各个接入方都需要自定义一套;使用后者,初期是省事了,后来维护的人员就懵逼了。这都无形中增加了很多维护成本。
Versions Maven 插件简介

Versions Maven 插件简介

D瓜哥
在 制定组织内 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>
升级 Maven 插件

升级 Maven 插件

D瓜哥
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,但是执行测试代码时,可能报错。可以使用如下配置来解决这个问题:
升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 的影响

升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 的影响

D瓜哥
在 关于升级 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 数据访问的异常类基类。
细说编码与字符集

细说编码与字符集

D瓜哥
文章还没写完,提前放出防止出现 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 代码块

D瓜哥
在 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 虚拟机操作码探秘:常量指令

Java 虚拟机操作码探秘:常量指令

D瓜哥
在 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”,暂时没有找到使用方法。就不做多介绍,后续看到相关资料,再做补充。
制定组织内 Maven BOM 的一些规范

制定组织内 Maven BOM 的一些规范

D瓜哥
在 关于升级 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 虚拟机指令(操作码)集

D瓜哥
最近在研究 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