写点什么

一个简单的例子教会您使用 javap

作者:Jerry Wang
  • 2021 年 12 月 04 日
  • 本文字数:2400 字

    阅读完需:约 8 分钟

一个简单的例子教会您使用 javap

javap 是 JDK 自带的工具:



这篇文章使用下面这段简单的 Java 代码作为例子进行讲解。


class Outer {  Nested nested;  Nested getNested() {    return nested;  }}class Nested {  Inner inner;  Inner getInner() {    return inner;  }}class Inner {  String foo;  String getFoo() {    return foo;  }}public class NullableTest {  public static Outer getInitializedOuter(){    Outer outer = new Outer();    outer.nested = new Nested();    outer.nested.inner = new Inner();    outer.nested.inner.foo = "Jerry";    return outer;  }  /* null pointer exceptionprivate static void way0(){Outer outer = new Outer();System.out.println(outer.nested.inner.foo);}*/  public static void way1(){    Outer outer = getInitializedOuter();    if (outer != null && outer.nested != null && outer.nested.inner != null) {      System.out.println(outer.nested.inner.foo);    }  }  public static void main(String[] args) {    //way0();    way1();  }}
复制代码


使用下面的命令行对 NullableTest 进行反编译,以 java 编译器生成的字节码:


javap -v NullableTest >c:\code\1.txt



查看方法 way1()对应的字节码:



下面这个 wiki 包含了 java 字节码里每个指令的具体说明:


https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings



下面对 NullableTest 反编译得到的字节码做一些说明:


0: invokestatic #42 // Method getInitializedOuter:()Ljava8/Outer;


代表静态方法 getInitializedOuter 的调用, Ljava8/Outer 意思是该方法的返回类型是 Outer


3: astore_0


将上述静态方法调用返回的 outer 引用存储到局部变量中,局部变量的 id 为 0.


4: aload_0


因为在我前面的 Java 源代码中,我将静态方法返回的对象引用同 null 做了比较,因此使用指令 aload_0 将存储在代号为 0 的局部变量中的对象引用重新加载到栈上,此后才能和 null 做比较。


5: ifnull 41


这就是我在 Java 源代码里书写的 IF 分支。如果 IF 分支里检测的 outer 引用为 null,则直接返回了。体现在字节码就是,如果 ifnull 为 true,则跳转到第 41 行字节码,即直接返回。



如果 ifnull 不为 true,则继续执行下去。又将 outer 引用加载到栈上。


从字节码的分析可以观察到一个有趣的现象,再次看看我们的 IF 语句。


Java 编译时,编译器实际将其转换成了下面的写法:


if (outer == null )
return;
if( outer.nested == null )
return;
if( outer.nested.inner == null)
return;
System.out.println(outer.nested.inner.foo);
复制代码


这个事实可以通过下图得到确认。



javap 生成的字节码里的 LineNumberTable 也很有用。这张表里每行的 line 后面的数字代表 Java 源代码的序号,line XX 冒号后面的数字代表字节码里每行指令的序号。看看下图中 Java 源代码和对应的字节指令在 LineNumberTable 中的映射关系。



LineNumberTable 维护了 Java 源代码同字节指令的映射关系,确保了 Java 代码调试的顺利进行。


不知道大家对千篇一律的 404 Not Found 的错误页面是否感到腻歪了?其实通过很简单的配置就能够让 Spring MVC 显示您自定义的 404 Not Found 错误页面。


在 WEB-INF 的 web.xml 里添加一个新的区域:



意思是一旦有 404 错误发生时,显示 resouces 文件夹下的 404.jsp 页面。


<error-page>
<error-code>404</error-code>
<location>/resources/404.jsp</location>
</error-page>
复制代码


现在可以随意开发您喜欢的个性化 404 错误页面了。




完毕之后,随便访问一个不存在的 url,故意造成 404 错误,就能看到我们刚才配置的自定义 404 Not Found 页面了。



如果想在 Spring MVC 里实现一个通用的异常处理逻辑(Exception handler), 能够捕捉所有类型的异常,比如通过下面这种方式抛出的异常,可以按照下面介绍的步骤来做。



1. 新建一个类,继承自 SimpleMappingExceptionResolver:


public class GlobalDefaultExceptionHandler extends
SimpleMappingExceptionResolver {
public GlobalDefaultExceptionHandler(){
System.out.println("GlobalDefaultExceptionHandler constructor called!");
}
@Override
public String buildLogMessage(Exception ex, HttpServletRequest request) {
System.out.println("Exception caught by Jerry");
ex.printStackTrace();
return "Spring MVC exception: " + ex.getLocalizedMessage();
}
复制代码


2. 在 Spring MVC 的 Servlet 配置文件里,将刚才创建的类作为一个 Bean 配置进去:



Bean 的 ID 设置为 simpleMappingExceptionResolver,class 设置为步骤一创建的类的包含 namespace 的全名。创建一个名为 defaultErrorView 的 property,其 value 为 generic_error, 指向一个 JSP view:generic_error.jsp。


<bean id="simpleMappingExceptionResolver" class="com.sap.exception.GlobalDefaultExceptionHandler">
<property name="exceptionMappings">
<map>
<entry key="Exception" value="generic_error"></entry>
</map>
</property>
<property name="defaultErrorView" value="generic_error"/>
</bean>
复制代码


generic_error.jsp 的源代码:


<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Generic Error Page of Jerry</title>
</head>
<body>
<h2>Unknown Error Occured, please contact Wang, Jerry.</h2>
</body>
</html>
复制代码


现在可以做测试了。我之前通过下列语句抛了一个异常:


throw new Exception("Generic Exception raised by Jerry");


这个异常成功地被我自己实现的异常处理类捕捉到,并显示出我自定义的异常显示页面:



要获取更多 Jerry 的原创技术文章,请关注公众号"汪子熙"。


发布于: 2 小时前阅读数: 9
用户头像

Jerry Wang

关注

个人微信公众号:汪子熙 2017.12.03 加入

SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使。

评论

发布
暂无评论
一个简单的例子教会您使用 javap