System.out.println(javaClass.getSuperclassName());
JavaClass javaClass1 = javaClass.getSuperClass();
the first line output the name of the class: RestController
The second line throws Exception:
java.lang.ClassNotFoundException: Exception while looking for class example.RestController: java.io.IOException: Couldn't find: example/RestController.class
So, you're using The Byte Code Engineering Library (Apache Commons BCEL™) to load classes from a Jar file (by filename) and want to print the entire call graph with the following github project.
This works just fine, until you want to ask for the superclass
of a class that was found in your jar file.
So bcel
will load a .class file, and store all it can read from the class file in a JavaClass
model. This is for example the name, some flags, the super class name, the declared methods etc.
For example inspect the following java code;
JavaClass stringClass = Repository.lookupClass("java.lang.String");
System.out.println(stringClass);
with output:
public final class java.lang.String extends java.lang.Object
implements java.io.Serializable, java.lang.Comparable, java.lang.CharSequence
file name java.lang.String
compiled from String.java
compiler version 52.0
access flags 49
constant pool 540 entries
ACC_SUPER flag true
Attribute(s):
SourceFile: String.java
etc...
So bcel knows that the superclass is java.lang.Object
, but it has not loaded any of the classes at this point! For JRE classes this is of course moot, but for the classes from your Jar file this is a problem.
Because org.apache.bcel.classfile.JavaClass#getSuperclassName
will just return the String
value that it found as the super class in the .class file. Again this class was not loaded, so the Repository
doesn't know about it.
When you then ask for the org.apache.bcel.classfile.JavaClass#getSuperClass
, it will try to find it like so:
public JavaClass getSuperClass() throws ClassNotFoundException {
return "java.lang.Object".equals(this.getClassName()) ? null : this.repository.loadClass(this.getSuperclassName());
}
Bcel will try to load it from its Respository
, and if the class is unknown, it will delegate the loading to the current ClassPath
. Since you're just inputting a File
pointing to a Jar, this will fail with the ClassNotFoundException
.
There are two ways to you can solve this:
Put the jar file(s) on your classpath; for example via Including all the jars in a directory within the Java classpath
First load all the jar files into the Repository
of bcel, so that it knows these classes exist. If we stay in the JCallGraph
example from github, that would look something like this:
// ... JCallGraph code from Github above this point
try (JarFile jar = new JarFile(f)) {
// extra stream over jar entries
Stream<JarEntry> jarEntryStream = enumerationAsStream(jar.entries());
jarEntryStream.filter(jarEntry -> jarEntry.getName().endsWith(".class"))
.forEach(jarEntry -> {
ClassParser cp = new ClassParser(jarFileName, jarEntry.getName());
try {
// here we tell BCEL this class exists
Repository.addClass(cp.parse());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
// ... back to the JCallGraph code from Github
Stream<JarEntry> entries = enumerationAsStream(jar.entries());
Note that if you have multiple jar files, or super classes coming from external dependencies, these all need to be on the classpath (1) or loaded in bcel first (2) before you can load the superclass.