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.
or just use the JavaLoader.cfc (http://www.compoundtheory.com/?action=javaloader.index) ...
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.
And it's a great explanation and makes total sense, thanks!