
When you run an agent in Notes/Domino the agent class is loaded into memory by a classloader (for more information about what a classloader actually is please refer to this previous post). In the case of Notes and Domino this classloader is the AgentLoader class from the lotus.domino package (that is lotus.domino.AgentLoader).
This is also the classloader which is responsible for loading all the other classes your agent and auxillary classes may use during their execution.
You can query any class in Java about which classloader actually loaded the class into memory. You do this using the getClassLoader()-method of the class instance as shown below:
import lotus.domino.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// get class for the current object
Class clazz = this.getClass();
// get classloader that loaded this agent class
ClassLoader cl = clazz.getClassLoader();
System.out.println("Default ClassLoader: " + cl.getClass().getName());
} catch(Exception e) {
e.printStackTrace();
}
}
}
The output of this agent in the Java Debug Console will be:
Default ClassLoader: lotus.domino.AgentLoader
In Java every classloader may be part of a hierarchy and hence a classloader may have a parent classloader. The only classloader that never has a parent is the top classloader called the bootstrap classloader which is installed by the JVM when initialized. The bootstrap classloader cannot be substituted in the case of Notes/Domino.
If a classloader is asked to load a class and it cannot find it, the request is delegated to the parent classloader if one is present. To demonstrate this we first need an extra classloader – let’s write our own classloader that loads classes from outside the Notes/Domino system classpath. Writing ones own classloader is actually VERY simple. You simply have to extend the java.lang.ClassLoader class and override one method. The code below shows how to write a classloader that loads classes from the root of the C-drive:
import java.io.*;
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super();
}
public MyClassLoader(ClassLoader cl) {
super(cl);
}
public Class findClass(String name) throws ClassNotFoundException {
try {
// use utility method to load class bytes from
// the root of the C-drive
byte[] b = this.loadClassData(name);
return this.defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
private byte[] loadClassData(String name) throws Exception {
// open a the requested file and read the bytes into an array
InputStream in = new BufferedInputStream(
new FileInputStream("c:\" + name + ".class"));
byte[] raw_classdata = new byte[1024];
int length = in.read(raw_classdata);
// since the byte array must be exactly the
// correct length we copy the bytes into a
// new array
byte[] classdata = new byte[length];
for (int i=0; i<length; i++) {
classdata[i] = raw_classdata[i];
}
// return the byte array
return classdata;
}
}
As you can see it is quite easy and only the imagination sets limits as to where you could load classes from in your agents. You could actually write a classloader to load classes from a central class repository using some network protocol of your choosing.
Since we need some classes to load using our new classloader I have written two very simple classes called OutsideClasspath and OutsideClasspath2 as shown below. The classes should be compiled using the javac-tool or using Eclipse and placed in the root of your C-drive. If you do not want to compile the classes yourself you can download them using the links below.
public class OutsideClasspath {
public OutsideClasspath() {
ClassLoader cl = this.getClass().getClassLoader();
System.out.println("Constructor of OutsideClasspath class " +
"(loaded by: " + cl.getClass().getName() + ")...");
// load another class
OutsideClasspath2 oc2 = new OutsideClasspath2();
System.out.println(oc2.echo("Hello from OutsideClasspath"));
}
}
public class OutsideClasspath2 {
public OutsideClasspath2() {
ClassLoader cl = this.getClass().getClassLoader();
System.out.println("Constructor of OutsideClasspath2 class " +
"(loaded by: " + cl.getClass().getName() + ")...");
}
public String echo(String echo) {
return "Echo: " + echo;
}
}
Using our new classloader is also simple. We simply instantiate it and pass the bootstrap classloader as the parent classloader in the constructor.
import lotus.domino.*;
import java.util.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// get bootstrap classloader
ClassLoader cl = this.getClass().getClassLoader();
System.out.println("Default ClassLoader: " + cl.getClass().getName());
// get classloader of the agent and show class name
System.out.println("Agent class loaded by: " +
this.getClass().getClassLoader().getClass().getName());
// create our own classloader passing the
// bootstrap classloader in the constructor
cl = new MyClassLoader(cl);
Class c = cl.loadClass("OutsideClasspath");
Object o = c.newInstance();
// create a java.util.Date object using our own classloader
// to demonstrate the hierarchical nature and delegation
// model of classloaders
Class clazz_date = cl.loadClass("java.util.Date");
Date d = (Date)clazz_date.newInstance();
System.out.println(d);
} catch(Exception e) {
e.printStackTrace();
}
}
}
The output of the agent is as follows:
Default ClassLoader: lotus.domino.AgentLoader
Agent class loaded by: lotus.domino.AgentLoader
Constructor of OutsideClasspath class (loaded by: MyClassLoader)...
Constructor of OutsideClasspath2 class (loaded by: MyClassLoader)...
Echo: Hello from OutsideClasspath
Sat Apr 01 22:13:49 CEST 2006
The output demonstrates a couple of things:
- The agent class is loaded by the bootstrap classloader.
- That we are able to load classes using our own classloader what is we can load and instantiate an instance of the OutsideClasspath class.
- That classes use the classloader that loaded them to load any classes they need. This is demonstrated by the fact that the OutsideClasspath2 class is automatically loaded by MyClassLoader when the OutsideClasspath class creates and instance of it.
- That classloaders delegate the loading of classes to their parent if they cannot find the class. This is demonstrated by the fact that the request to load java.util.Date is delegated to the bootstrap classloader.
I hope this post has demonstrated the power of classloaders and how you can easily write one that suits your purpose.
Please note:
- Even if we had not supplied the bootstrap classloader in the constructor of the MyClassLoader class a ClassNotFoundException exception would not have been thrown at runtime when asked to load the java.util.Date class. This is because the bootstrap classloader is always asked as a last resort. Setting the parent classloader is only important if you build your own hierarchy of classloaders as is the case in J2EE application servers.
- The MyClassLoader class should be part of the agent if you decide to try the code out for yourself in Domino Designer.