|
자바 클래스 뷰어입니다.
코드 프로젝트 펌입니다.
http://www.codeproject.com/KB/java/javaclassviewer.aspx
===============================
Please use NetBeans version 6.5 or above to open the attached source code. You need to update the jCFL library reference of the NetBeans project since your folder path may be different.
You may come to the jCFL download page to get the latest library.
Java Class File is one of the key reasons that Java can run on various platforms. The Java class file is designed as a byte stream, with a specific structure as described in the JVM Spec, chapter 4.
Well, it is not easy for a beginner to understand the VM Spec; Java Class Viewer is a visual yet powerful application which can show the meaning of each byte of the class file.
You can ignore this "Background" section if you are not interested in the history of Java Class Viewer.
Years ago, I was asked to write a plug-in which can hook the Java applications. The principle of the plug-in was simple: Find the object/class at run time, and try to change its behavior. At that time, I had to read the class file byte by byte via the binary file reader like UltraEdit. It is really not interesting at all; it is boring.
So, I decided to write a Java Class Viewer, which can display the class file visually, and it can display the meaning of each byte of the class file. This is the reason for creating the Java Class Viewer (jCV) application.
In the very beginning (September, 2007), jCV was a command line tool. And one and a half years later (I am lazy enough...), the graphical version of this application was created.
When creating the graphical application, I noticed that this application can be divided into two parts:
javac
error message if the class file has any problem.
So, for now, there are two components: jCFL, and jCV.
Another reason for this is that, jCFL can also be used in other areas, like class file Meta data analysis.
There is some other library available for class file manufacturing and verifying, like the Byte Code Engineering Library (BCEL) from Apache. The design principle of jCFL is quite different from BCEL. BCEL is a powerful library for edit/change Java class file, and there is also a class file verifier available in BCEL.
Well, it is (almost) impossible to use BCEL to write a Class File Viewer which can show each byte's meaning, since it does not record the location when parsing the class file; BCEL is designed following the author's idea, the idea and naming convention is not following the JVM Spec, Class File Structure strictly.
jCFL could edit class file until now; jCFL records the offset of the class
file when parsing; jCFL follows the ClassFile
structure in JVM Spec
strictly.
If you are running jCV, while reading the JVM Spec, it will be much easier for a beginner to understand the Class file.
The Class file has the following structure:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
You may want to view the section The
class File Format in the JVM Spec for a detailed description for the
ClassFile
structure. Here is a brief description:
magic
- 0xCAFEBABE, the magic number of class file. If the first
4 bytes are not 0xCAFEBABE, it is not recognized as a class file.
minor_version, major_version
- The major version and minor
version determine the class file version together.
constant_pool_count, cp_info constant_pool[constant_pool_count-1]
- Constant pool of the class file. The
constant pool may contain eleven types of constants:
01. class/interface info
02. field reference info
03. method reference info
04. interface method reference info
05. String
06. Integer
07. Float
08. Long
09. Double
10. NameAndType
11. Utf8
access_flags
- The access flag of the class
this_class
, super_class
- The class info of current
class and super class. Only the java.lang.Object
class's super class is
null
; if the super class is not specified for this class, the super
class is java.lang.Object
.
interfaces_count, interfaces[interfaces_count]
- The
direct super interfaces.
fields_count, field_info fields[fields_count]
- The fields of
this class, if there are any.
methods_count, method_info methods[methods_count]
- The methods
of this class. The Java compiler will generate a default constructor for a class
(inner class excluded) if there is none. So, there will be at least one
method in this class.
attributes_count, attribute_info attributes[attributes_count]
-
The attribute of this class. There is at least one attribute named
"SourceFile
" for the file name of the source code.
The class org.freeinternals.classfile.core.ClassFile
is the
parser of class file. It accepts a byte array as input parameter; the byte array
contains the class file. The byte array may come from a .class file, a
.jar file, a .war file, etc. Or alternatively, the byte array
may be built by libraries like BCEL.
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractClassFile()
File file = new File("C:/Temp/File.class");
byte[] classByteArray = Tool.readClassFile(file);
ClassFile classfile = new ClassFile(classByteArray);
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractJarFile()
File file = new File("C:/Temp/tools.jar");
JarFile jarFile = new JarFile(file, false, JarFile.OPEN_READ);
ZipFile zipFile = jarFile;
final Enumeration zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement();
if (!zipEntry.getName().endsWith(".class")) {
continue;
}
byte[] classByteArray = Tool.readClassFile(zipFile, zipEntry);
ClassFile classfile = new ClassFile(classByteArray);
System.out.println();
System.out.println(zipEntry.getName());
jCFL_CodeDemo.printClassFile(classfile);
}
There is a tool class org.freeinternals.classfile.ui.Tool
which
can help us to read from a file or a zip file. We know that .jar and
.war files are both in fact in zip format.
The constructor of ClassFile
throws an
org.freeinternals.classfile.core.ClassFormatException
if the byte
array is not a valid class file, or a java.io.IOException
if there
is some error in IO. You may surround the statement with try...catch
block or add a throws
clause in the method declaration.
Once we get the ClassFile
instance successfully, we can get the
information of the class file via the getXxxxx
methods.
Here is an example to print out all of the component information of the class file:
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.printClassFile()
// Minor & Major version
MinorVersion minorVersion = classfile.getMinorVersion();
System.out.println("Class File Minor Version: " + minorVersion.getValue());
MajorVersion majorVersion = classfile.getMajorVersion();
System.out.println("Class File Major Version: " + majorVersion.getValue());
// Constant Pool
CPCount cpCount = classfile.getCPCount();
System.out.println("Constant Pool size: " + cpCount.getValue());
AbstractCPInfo[] cpArray = classfile.getConstantPool();
for (int i = 1; i < cpCount.getValue(); i++) {
System.out.println(
String.format("Constant Pool [%d]: %s", i, classfile.getCPDescription(i)));
short tag = cpArray[i].getTag();
if ((tag == AbstractCPInfo.CONSTANT_Double) ||
(tag == AbstractCPInfo.CONSTANT_Long)) {
i++;
}
}
// Access flag, this & super class
AccessFlags accessFlags = classfile.getAccessFlags();
System.out.println("Class Modifier: " + accessFlags.getModifiers());
ThisClass thisClass = classfile.getThisClass();
System.out.println("This Class Name Index: " + thisClass.getValue());
System.out.println("This Class Name: " +
classfile.getCPDescription(thisClass.getValue()));
SuperClass superClass = classfile.getSuperClass();
System.out.println("Super Class Name Index: " + superClass.getValue());
if (superClass.getValue() == 0) {
System.out.println("Super Class Name: java.lang.Object");
} else {
System.out.println("Super Class Name: " +
classfile.getCPDescription(superClass.getValue()));
}
// Interfaces
InterfaceCount interfactCount = classfile.getInterfacesCount();
System.out.println("Interface Count: " + interfactCount.getValue());
if (interfactCount.getValue() > 0) {
Interface[] interfaceArray = classfile.getInterfaces();
for (int i = 0; i < interfaceArray.length; i++) {
System.out.println(
String.format("Interface [%d] Name Index: %d", i,
interfaceArray[i].getValue()));
System.out.println(
String.format("Interface [%d] Name: %s", i,
classfile.getCPDescription(interfaceArray[i].getValue())));
}
}
// Fields
FieldCount fieldCount = classfile.getFieldCount();
System.out.println("Field count: " + fieldCount.getValue());
if (fieldCount.getValue() > 0) {
FieldInfo[] fieldArray = classfile.getFields();
for (int i = 0; i < fieldArray.length; i++) {
System.out.println(String.format("Field [%d]: %s", i,
fieldArray[i].getDeclaration()));
}
}
// Methods
MethodCount methodCount = classfile.getMethodCount();
System.out.println("Method count: " + methodCount.getValue());
if (methodCount.getValue() > 0) {
MethodInfo[] methodArray = classfile.getMethods();
for (int i = 0; i < methodArray.length; i++) {
System.out.println(String.format("Method [%d]: %s", i,
methodArray[i].getDeclaration()));
}
}
// Attributes
AttributeCount attributeCount = classfile.getAttributeCount();
System.out.println("Attribute count: " + attributeCount.getValue());
AttributeInfo[] attributeArray = classfile.getAttributes();
for (int i = 0; i < attributeArray.length; i++) {
System.out.println(String.format("Attribute [%d]: %s", i,
attributeArray[i].getName()));
}
Here are some special notes for the code above.
Constant Pool: The constant pool array is from index
1
to (constant_pool_count-1
); and the CONSTANT_Long_info
and
CONSTANT_Double_info
will take two indexes position while all other
types will take only one position.
Super Class: The super class index will be zero only when
the current class is java.lang.Object
. Otherwise, it should be an item in
the constant pool.
Interfaces & Fields: One class may have no interfaces or
fields, so we need to check the InterfaceCount
and
FieldCount
variable before getting the array for interface/field.
Methods: For a non-inner class, one class must have at least
one method, which is the default instance constructor created by
javac
; but for inner class, there can be no method. So we should
check the MethodCount
variable is not zero.
Attributes: One class must have at least one attribute, the
SourceFile
attribute; we don't have to add similar logic for
it.
By using some code just like the above, it is not hard work to write a visual UI control for a class file. And it will be very easy for us to write any application to analyse the meta data in the class.
To reduce the effort to write a Java Class Viewer, the jCFL provides a set of swing UI controls.
The class org.freeinternals.classfile.ui.JTreeClassFile
is a
subclass of JTree
, which accepts a ClassFile
object in
the constructor. It will add all components of a class file into the tree
control.
The class org.freeinternals.classfile.ui.JSplitPaneClassFile
is
a subclass of JSplitPane
, which is divided into two panels: the
left panel is the JTreeClassFile
, while the right panel is a binary
viewer for class file.
While we select each component in the tree, the corresponding bytes will be highlighted; this is the reason for the word "interactive".
// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_ClassFile()
private JSplitPaneClassFile cfPane;
private void open_ClassFile(final File file) {
this.cfPane = new JSplitPaneClassFile(Tool.readClassFile(file));
this.add(this.cfPane, BorderLayout.CENTER);
this.resizeForContent();
}
For example, after opening the class file File.class
(java.io.File
), when we select the node for the method
getName()
, the corresponding bytes will be highlighted.
If we select the name_index
, descriptor_index
node
for this method, only the index section (for the value 119, 24) will be
highlighted.
If we select the code
node in the Code
attribute,
and open the Opcode tab, the opcodes of this method are extracted.
Java Class Viewer is not intended to be a decompiler, it only displays the raw code and some comments according to the context. You may refer to the JVM Spec for the meaning of the opcode. The extracted opcode is partly human readable.
The class org.freeinternals.classfile.ui.JTreeZipFile
is a
subclass of JTree
, which accepts a ZipFile
in the
constructor. It will build a tree for all entries in the zip file. The
.jar/.war file is in fact a zip file.
// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_JarFile()
// Only key logic is left here
private JTreeZipFile zftree;
private void open_JarFile(final File file) {
this.zftree = new JTreeZipFile(new JarFile(file, false, JarFile.OPEN_READ));
this.zftreeContainer = new JPanelForTree(this.zftree);
this.add(this.zftreeContainer, BorderLayout.CENTER);
this.resizeForContent();
}
Here is a screen shot of the ZipFile
tree control.
BTW, if we double click an xxxxx.class node, a new window will be opened for the class file.
By using the swing controls provided by jCFL, it is very easy to write a
class file viewer. All we need to do is to add menu/toolbar, and layout the
controls onto a JFrame
.
You may need to refer to the source code at the top of this article for details.
The class file viewer is only the starting point to understand Java. We may use it as a tool to study the new feathers provided by new versions of Java; as a tool to study AOP, to study some framework/platform code, etc.
Sun (may be Oracle later) is trying to keep the compatibility of the class file format. For example, annotation in fact is adding some attribute to the class file, and uses reflection mechanism to read and analyse annotation; thus annotation's performance is poor, but this is how we can keep compatibility.
|