为了能够更加深入的学习class结构,本章节将写一个ClassByteCodeParser类(有极小部分数据结构较复杂没解析)来实现简单的class文件解析。
    首先我们创建一个用于测试的TestHelloWorld.java文件,源码如下:

    1. package com.anbai.sec.bytecode;
    2. import java.io.Serializable;
    3. /**
    4. * Creator: yz
    5. * Date: 2019/12/17
    6. */
    7. @Deprecated
    8. public class TestHelloWorld implements Serializable {
    9. private static final long serialVersionUID = -7366591802115333975L;
    10. private long id = 1l;
    11. private String username;
    12. private String password;
    13. public String hello(String content) {
    14. String str = "Hello:";
    15. return str + content;
    16. }
    17. public static void main(String[] args) {
    18. TestHelloWorld test = new TestHelloWorld();
    19. String str = test.hello("Hello World~");
    20. System.out.println(str);
    21. }
    22. public long getId() {
    23. return id;
    24. }
    25. public void setId(long id) {
    26. this.id = id;
    27. }
    28. public String getUsername() {
    29. return username;
    30. }
    31. public void setUsername(String username) {
    32. this.username = username;
    33. }
    34. public String getPassword() {
    35. return password;
    36. }
    37. public void setPassword(String password) {
    38. this.password = password;
    39. }
    40. @Override
    41. public String toString() {
    42. return "TestHelloWorld{" +
    43. "id=" + id +
    44. ", username='" + username + '\'' +
    45. ", password='" + password + '\'' +
    46. '}';
    47. }
    48. }

    然后使用javacTestHelloWorld.java编译成TestHelloWorld.class文件,或者使用maven构建javaweb-sec/javaweb-sec-source/javase/项目,构建成功后在javaweb-sec/javaweb-sec-source/javase/target/classes/com/anbai/sec/bytecode/目录下可以找到TestHelloWorld.class文件。

    最后编写一个ClassByteCodeParser类,严格按照JVM规范中的类文件格式文档规定,依次解析class文件的各种数据类型就可以实现字节码解析了。
    ClassByteCodeParser代码片段(省略了getter/setter和解析逻辑):

    1. package com.anbai.sec.bytecode;
    2. /**
    3. * Java类字节码解析,参考:https://docs.oracle.com/javase/specs/jvms/se15/jvms15.pdf和https://github.com/ingokegel/jclasslib
    4. */
    5. public class ClassByteCodeParser {
    6. /**
    7. * 转换为数据输入流
    8. */
    9. private DataInputStream dis;
    10. /**
    11. * Class文件魔数
    12. */
    13. private int magic;
    14. /**
    15. * Class小版本号
    16. */
    17. private int minor;
    18. /**
    19. * Class大版本号
    20. */
    21. private int major;
    22. /**
    23. * 常量池中的对象数量
    24. */
    25. private int poolCount;
    26. /**
    27. * 创建常量池Map
    28. */
    29. private final Map<Integer, Map<String, Object>> constantPoolMap = new LinkedHashMap<>();
    30. /**
    31. * 类访问修饰符
    32. */
    33. private int accessFlags;
    34. /**
    35. * thisClass
    36. */
    37. private String thisClass;
    38. /**
    39. * superClass
    40. */
    41. private String superClass;
    42. /**
    43. * 接口数
    44. */
    45. private int interfacesCount;
    46. /**
    47. * 接口Index数组
    48. */
    49. private String[] interfaces;
    50. /**
    51. * 成员变量数量
    52. */
    53. private int fieldsCount;
    54. /**
    55. * 成员变量数组
    56. */
    57. private final Set<Map<String, Object>> fieldList = new HashSet<>();
    58. /**
    59. * 方法数
    60. */
    61. private int methodsCount;
    62. /**
    63. * 方法数组
    64. */
    65. private final Set<Map<String, Object>> methodList = new HashSet<>();
    66. /**
    67. * 属性数
    68. */
    69. private int attributesCount;
    70. /**
    71. * 属性
    72. */
    73. private Map<String, Object> attributes;
    74. /**
    75. * 解析Class字节码
    76. *
    77. * @param in 类字节码输入流
    78. * @throws IOException 解析IO异常
    79. */
    80. private void parseByteCode(InputStream in) throws IOException {
    81. // 将输入流转换成DataInputStream
    82. this.dis = new DataInputStream(in);
    83. // 解析字节码逻辑代码
    84. }
    85. public static void main(String[] args) throws IOException {
    86. // 解析单个class文件
    87. File classFile = new File(System.getProperty("user.dir"), "javaweb-sec-source/javase/target/classes/com/anbai/sec/bytecode/TestHelloWorld.class");
    88. ClassByteCodeParser codeParser = new ClassByteCodeParser();
    89. codeParser.parseByteCode(new FileInputStream(classFile));
    90. System.out.println(JSON.toJSONString(codeParser));
    91. }
    92. }

    解析完TestHelloWorld.class后将会生成一个json字符串,省略掉复杂的constantPoolMapfieldListmethodListattributes属性后格式如下:

    1. {
    2. "accessFlags": 33,
    3. "attributes": {},
    4. "attributesCount": 3,
    5. "constantPoolMap": {},
    6. "fieldList": [],
    7. "fieldsCount": 4,
    8. "interfaces": [
    9. "java/io/Serializable"
    10. ],
    11. "interfacesCount": 1,
    12. "magic": -889275714,
    13. "major": 51,
    14. "methodList": [],
    15. "methodsCount": 10,
    16. "minor": 0,
    17. "poolCount": 95,
    18. "superClass": "java/lang/Object",
    19. "thisClass": "com/anbai/sec/bytecode/TestHelloWorld"
    20. }