Loading Local Java Class Files Using URLClassLoader
Ok, this is nothing new. In fact, this isn't even for YOU. This is for me. This is coming right off of Spike-Fu's blog entry titled "Loading java class files from a relative path." He wrote it back in 2004; I am simply rewriting it here in my own words so that I can explain it to myself and see that it actually works and see if actually understand what the heck is going on. It's a post that I have know about for a long time but have just never taken the time to get into it. Now is that time.
If you follow my blog closely, you will know that I don't like things like class paths, path mapping, or basically, anything that requires the need to have special permissions on the hosting machine (ie. access to the admin or secured file system). Obviously, data sources are required, but that is once per project. I hate the idea of having to go back to the ColdFusion admin for much of anything. I like my projects to be very module. I like the idea of picking it up, dumping it somewhere else and just having it WORK. I know this creates duplicate code (for all you framekwork people) but I am sooo OK with that it's not even funny.
The ability to be able to load Java class files without them being in the ColdFusion list of Java class paths is something that fits into this ColdFusion application development mentality.
Ok, so let's look at the example. I downloaded Spike's HelloWorld.class file and have placed it in the same directory of the calling code. Then, I loaded it and called a method on it to see that it was working.
<!---
Initialize the URL object to point to the current
directory. In this case we are pointing using the file
system, NOT the web browser, hence the "file:" prefix. This
URL can point to any part of the file system irrelevant of
the web-accessible parts of the system. This will happen on
the server size, not the client side (of course) and
therefore is not dependent on web-accessibility.
--->
<cfset objUrl = CreateObject( "java", "java.net.URL" ).Init(
JavaCast(
"string",
"file:" & ExpandPath( "./" )
)
) />
<!---
Get an array of URL objects. Since we cannot do this
directly, (delcare an array of java object types), we will
have to do this using the reflect array class. We can pass
this class a Java class definition and ask it to create an
array of that type for us.
--->
<cfset objArrayCreator = CreateObject(
"java",
"java.lang.reflect.Array"
) />
<!---
Ask the reflect array service to create an array of URL
objects. Get the class definition from the URL object we
created previously. When we create the array, we need to
create it with the length of 1 so that we can add the URL
to it next.
--->
<cfset arrURL = objArrayCreator.NewInstance(
objUrl.GetClass(),
JavaCast( "int", 1 )
) />
<!---
Now, we need to set the URLs into the array. This array
will be used for the initialization of the URL class loader
and will need to contain all the URLs that we need. Since
we cannot work with these types of array directly in
ColdFusion, we need to ask our array creator object to do
the setting for us.
My hope was that we did NOT have to use this SET method. I
had hoped that I could call the AddURL() method on the
URLClassLoader itself. Unforutnately, that method is
Protected and I do not have permissions to access it.
Hence, all of the URLs that we want to load have to be
passed in during the initialization process.
--->
<cfset objArrayCreator.Set(
arrURL,
JavaCast( "int", 0 ),
objUrl
) />
<!---
Now, we want to create a URL class loader to load in the
Java classes that we have locally. In order to initialize
this class, we need to pass it the URL array that we just
created. Keep in mind that it contains all the URLs that
we want to load.
--->
<cfset objClassLoader = CreateObject(
"java",
"java.net.URLClassLoader"
).Init(
arrURL
)
/>
<!---
Now, get the URL class loader to load the HelloWorld.class
class. Once it loads the class, we have to get a new
instance of the Java class, HelloWorld.
--->
<cfset objHelloWorld = objClassLoader.LoadClass(
"HelloWorld"
).NewInstance() />
<!---
Get the hello world object to perform an action. This should
output the phrase "Hello World". This will show us that
everything is working nicely.
--->
<cfoutput>
#objHelloWorld.SayHello()#<br />
</cfoutput>
This outputs the phrase "Hello World!" It works quite nicely and very fast, at least on this tiny example. Still, very very cool that you can load Java classes without having to mess with the Admin, which you know I think is highly sexy.
Just a few notes. You will see when we load the class, we then call the Java method NewInstance() on the class. I don't fully understand how all this works, but I guess the first part just loads the definition for the class then the new instance call loads a new instance of that class. If you look at the Java 2 documentation, you will see that NewInstance():
"Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized."
This is very interesting. If you use new instance, you cannot pass in an initialization list. That's good to know for future use (and experimentation).
Want to use code from this post? Check out the license.
Reader Comments
or just use the JavaLoader.cfc (http://www.compoundtheory.com/?action=javaloader.index) ...
Rob,
I appreciate the link. It looks like the JavaLoader is doing the same thing that Spike was, just in a more packaged object. But, my intent was not so much to be able to do it, but more to understand how it worked and just to explore that it was indeed possible.
Thanks.
And it's a great explanation and makes total sense, thanks!