I've been doing a ridiculous amount of work recently writing up some Java code that I am looking to deploy with Transfer, and in the process have had all sorts of fun running around exploring the internals of ColdFusion.
The issue however was being able to deploy the code with my library.
(As per usual with these posts, you must note that some of these methods are undocumented and liable to change, yadda ya)
I had my JAR file that I wanted to be able to use with my ColdFusion code, but there were several problems
1. The Java code referenced some ColdFusion, and other related JAR files that were loaded into CF at startup
2. Some of my objects I wanted to be able to create via CF have constructors that require arguments, which is tricky business.
3. ColdFusion likes to keep a 'hold' on any Jar it loads up via URLClassLoader, so being able to redeploy/upgrade the code is tricky, as you can't delete the file without resetting the server.
Okay, so we'll start at the beginning – obviously we have the initial aspect of using a URLClassLoader to load in the external series of .class files, or .jar files, as Spike wrote about back in 2004 . (I'm not going to cover that aspect of it here, there is plenty of examples out now on how to use URLClassLoader out on the blogsphere)
This is all fine and dandy, I load in my class, and I try and create an instance, but it will throw an error – you know why? Because the code in my JAR references Java classes that the URLClassLoader doesn't know about. Namely ColdFusion classes! So it throws a big fat hairy error as it can't see them.
Now, the neat thing is, a URLClassLoader's constructor can take a java.lang.ClassLoader as it's parent – which means it can look to it to try and resolve classes. So off we run to get the ClassLoader for the ColdFusion context.
There are about half a dozen different ways of doing this, but I did it this way -
cfClassLoader = getClass().getClassLoader();
getClass() gets the java.lang.Class object of the current page or cfc, which is very handy for us, as we can grab the ClassLoader for the CF context, and thereby resolve classes that are loaded by ColdFusion when it starts up.
So now, when we create our URLClassLoader, we can create it like this:
classLoader = createObject("java", "java.net.URLClassLoader").init(URLs, getClass().getClassLoader());
Where URLs is the array of java.net.URL that we need to tell the URLClassLoader what directories and jar files to load in.
Okay, so that's all well and good, but problem number 2 arises. URLClassLoader allows us to make calls like:
string = urlclassLoader.loadClass("java.lang.String).getInstance();
To create an instance of a Java object. Which is all fine and dandy if there is a default constructor, BUT so often Java objects like to take arguments when they are instantiated.
I beat my head against this one for a while, as it is a real pain to try and do this through reflection – and then it dawned on me – ColdFusion has already done the hard work for us, why shouldn't we just take advantage?
So I did some digging around and worked out that when you write:
System = createObject("java", "java.lang.System");
The object 'System' is an instance of the coldfusion java object 'coldfusion.runtime.java.JavaProxy'. The JavaProxy takes a java.lang.Class as an argument in it's constructor, and then handles all the aspects of interpreting CF calls on Java objects.
So now, when we load up a class we want an instance of in ColdFusion, we do this -
class = getClassLoader().loadClass("myPersonalJavaObject");
personalObject = createObject("java", "coldfusion.runtime.java.JavaProxy").init(class);
This gives us access to the object as we would normally through a createObject("java", "className"); call.
So now, as we would normally, if we want to create an instance of the object we can call -
instance = personalObject.init();
Just like we would normally. But if we have arguments
instance = personalObject.init(myarg1, myarg2);
And ColdFusion will handle the aspect of translating the CF to the Java object for you.
Finally, the issue that, when you've made your URLClassLoader to a JAR file, the system often won't let you delete the file, as it still marks it as being 'under use'. The way I solved that problem was by using ANT to add a datestamp to the name of my jar flie when it creates it.
This does 2 things:
1. Gives the file a unique name, so I don't have to worry about deleting the previous JAR, and can do so when the server gets reset, or the garbage collector realises that nobody is using the JAR anymore, and releases it.
2. Lets me know when looking at the dir where I store my JAR files which file was created first.
Since I know which one was created first – as long as I load the most recent JAR file to the URLClassLoader first, it will be the first one queried for the class files that are being requested, and therefore you can use the objects that you want.
It also means you could also write some nifty code to ignore the previous version by tokenizing the name of the file, but I will leave that aspect up to you.
This code has yet to be committed to the Transfer CVS repository, but assuming it all goes ahead as planned, look in the package transfer.com.util for a JavaClassLoader to see these techniques in action.
I hope that all made sense, and makes life easier for you while you write your own Java code to run alongside ColdFusion.