
When doing Java development you write your code in classes and hence deal with classes. Each Java class in your source code is compiled into a binary class file.
JavaAgent.java (text) -(compile)-> JavaAgent.class (binary)
The compilation is done automatically for you when you write Java in Notes/Domino or if programming in Eclipse. It does however still help to be aware of what actually goes on under the covers when your agent is run.
“The process of finding the binary classes and loading them into memory is called classloading…”
The fact that each class is compiled into a separate file means that a Java agent consisting of 4 classes will contain 4 compiled classes. Since all the code isn’t combined into a separate file (like an EXE-file on Windows) the Java Virtual Machine (JVM) needs to locate these compiled classes at runtime and load them into memory so it can use them hence the word classloading.
The default classloader in Notes/Domino (lotus.domino.AgentLoader) is capable of loading classes from the classpath of the current agent which consists of:
- Classes that are part of the agent itself.
- Classes and JAR-files added to the agent using the “Edit Project”-button.
- Java Script libraries added to the agent using the “Edit Project”-button.
- Classes present on the Notes/Domino system classpath.
See the post “Managing external Java dependencies in Domino Designer” for more information on the Notes/Domino system classpath.
The exceptions
There are three exceptional situations that can and do occur frequently when working with Java in relation to classloading. These are the java.lang.ClassNotFoundException, the java.lang.NoClassDefFoundError and the java.lang.UnsupportedClassVersionError. Below are short excerpts from their explanation in the JDK. When programming in Notes/Domino you will probably encounter the first two at some point. The third I do not wish upon you… 🙂
| ClassNotFoundException | Thrown when an application tries to load in a class through its string name using the forName method in class Class. |
|---|---|
| NoClassDefFoundError | Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found. |
| UnsupportedClassVersionError | Thrown when the Java Virtual Machine attempts to read a class file and determines that the major and minor version numbers in the file are not supported. |
The ClassNotFoundException exception is encountered when you try to dynamically instantiate a class using a string name (java.lang.Class.forName()) and the class cannot be found in the classpath. This is a checked exception you must catch when doing a Class.forName() call.
The NoClassDefFoundError is raised if you use a class in your agent which in turn uses another class that cannot be loaded at runtime. The most common scenario for this is when using third-party library consisting of multiple JAR-files. In this case you may only have included one of the JAR-files in your agent to make it compile. The agent may compile but if classes in the included JAR-file uses classes in a second JAR-file, that wasn’t included in the agent, this error will occur.
The UnsupportedClassVersionError is less likely to occur if you are on Notes/Domino 6.x or 7.x but does occur quite a lot if you are using third-party libraries on older Notes/Domino versions. The binary Java class format changes slightly over time (much like the On-Disk-Structure of Notes databases) and since the JVM cannot be forward compatible it needs a way to tell you that you are trying to load a class into the JVM it doesn’t know how to handle (the version of the class format is wrong). The way around this is to try and compile the classes using a Java 1.1.8 compiler or using “classic” mode with newer compilers.
So why is this important?
To really appreciate the concept of classloading you need to realize that:
- Java classes are not loaded into memory before actually used.
- Java supports dynamic classloading.
- You can write your own classloader.
- Classloaders in Java are hierarchical and thus can delegate classloading to other classes.
You don’t have to worry too much about item 1. I’ll explain item 2 and 3 in detail in subsequent posts to avoid making this post too long. Item 4 deals with delegation of classloading in case a specific classloader cannot find a given class. Suffice it to say that these four concepts allows the JVM to conserve memory and you to write very powerful and highly dynamic agents/classes.
All this fuss about classloading might seem overly complex but consider the fact that many of the classes that you use in your Java agent aren’t some you have written. They are part of the Java Development Kit (the java.lang packages) or part of third-party libraries that you program and execute your agents against.
If there were no classloaders you could not have dynamic classloading and all classes would need to be loaded into memory before starting an agent. This could/would take up a lot of memory and be highly inefficient.
Nice summary. Thanks.
Interesting but incomplete in my experience. It seems that java bean objects are evaluated very carefully (maybe crippled by the Notes security container?) so that object creation from java agents is harder than it should be. Introspection is also contrained – example from running an agent in a local NSF:
org.apache.commons.lang.builder.ReflectionToStringBuilder will generate
All possible security limitations are turned off(All Readers & Above – Yes; Allow Public Access; Allow restricted operations w/full admin rights).
What can you say about a local db JavaAgent stacktrace like this:
Note: the *same* code run from cmd line returns beautiful, well formed TechCandidate objects emitted by Jakarta Digester & and an xml file.
Hope someone can shed some light on this situation.
-Parker
Well as I think I noted in another post (I think my post on the SecurityManager employed by Notes/Domino) most Apache Jakarta projects will not function properly in Notes/Domino without being placed in the jvm/lib/ext directory due to the way they use introspection, reflection and dynamic classloading. Sad but true.
Examples are log4j and Commons Logging.
I would guess that you get the ClassNotFoundException due to the way classloaders work. A class will always try to load classes it needs using the same classloader that loaded the class in the first place. Depending on the way Digester is written it could be because a custom classloader is installed and because the custom classloader doesn’t know of classes imported directly into agents and/or script libraries in Notes.
An issue like that should be resolvable by placing all classes, incl. your TechCandidate class, in jvm/lib/ext.
I think it is worth trying although it is far from an optimal solution.