Loading Java Classes With coldfusion.runtime.java.JavaProxy
Not so long ago, I loaded my first Java class into ColdFusion using Java's Url Class Loader. It's a pretty cool piece of functionality. It allows you to build highly portable applications by keeping the Java .jar and .class files with your application and using them without having to mess with ColdFusion's class paths (I am a HUGE fan of high-portability). From my limited grasp of Java, the Url Class Loader takes the class definition and creates a new instance of that class. From the Java 2 documentation, it seems that the returned class is initialized with an empty argument set before it is returned:
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 totally fine some of the time. However, some of the time this cannot work. If the class's constructor is not available or maybe it's a class that cannot be instantiated (again, I don't really know much about Java yet) this cannot be done. I ran into this just the other day when I was experimenting with the JExcel library. It had a class that I couldn't directly instantiate.
To overcome this, I used the JavaProxy object that I just found in Mark Mandel's JavaLoader.cfc. While I cannot find any documentation on this Java object (the Java Proxy, not Mark's stuff), what it seems to do is load a Java Class object without initializing it. Therefore, it seems the constructor is not called. Once you get the returned object, you can call INIT() on it if you want, but this is not required for static methods. Sorry if I am totally misleading here, but that is what I have gleamed from the code. Also, I don't fully understand the difference between a CLASS object and an object instance. That will come with time.
Here is my CreateJExcelObject() method that I used for experimentation. It basically takes my URL Class Loader example and hard codes the JAR file path (for the JExcel API code) and the JavaProxy object:
<cffunction
name="CreateJExcelObject"
access="public"
returntype="any"
output="false">
<!--- Define arguments. --->
<cfargument name="Class" type="string" required="true" />
<!--- Define the local scope. --->
<cfset var LOCAL = StructNew() />
<!---
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. In this case, we are hard-coding the
path to the JExcel JAR file, however this is a path that
could easily be passed in.
--->
<cfset LOCAL.Url = CreateObject( "java", "java.net.URL" ).Init(
JavaCast(
"string",
"file:" & ExpandPath( "./jxl.jar" )
)
) />
<!---
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 LOCAL.ArrayCreator = 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 LOCAL.URLArray = LOCAL.ArrayCreator.NewInstance(
LOCAL.Url.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 LOCAL.ArrayCreator.Set(
LOCAL.URLArray,
JavaCast( "int", 0 ),
LOCAL.Url
) />
<!---
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 LOCAL.ClassLoader = CreateObject(
"java",
"java.net.URLClassLoader"
).Init(
LOCAL.URLArray
)
/>
<!---
Use the JavaProxy object as demonstrated by Mark Mandel
and his JavaLoader.cfc. This will create a Java class
that has not been initialized. You can use the returned
class as a static class or you can call the INIT() method
on it with the appropriate arguments.
--->
<cfreturn
CreateObject(
"java",
"coldfusion.runtime.java.JavaProxy"
).Init(
LOCAL.ClassLoader.LoadClass(
("jxl." & ARGUMENTS.Class)
)
) />
</cffunction>
After looking over some Java documentation, it seems like the CLASS:ForName( strName, blnInitialize, objClassLoader ) should be able to do the same thing, but if I substitute this as the return statement:
<cfreturn
CreateObject( "java", "java.lang.Class" ).ForName(
("jxl." & ARGUMENTS.Class),
JavaCast( "boolean", false ),
LOCAL.ClassLoader
)
/>
... it bombs out. This is not surprising though, as I know very little about Java itself. I don't even fully understand the documentation when I read it.
I am, however, fairly blown away at the highly reflective and dynamic nature of the Java language. It's pretty freakin' impressive that you can go in and just grab classes from different places, load them, get lists of declared methods, find parameter types on the fly, and do just about anything with out knowing before hand what kind of objects you are dealing with. Well done Java!
I have to say, though, that it makes me very uncomfortable to use a class that is built into ColdFusion, especially when there is no documentation on it. Someone was able to get a hold of the source code for it and sent it to me. While I don't fully understand exactly how it works, I might compile it into a free standing class outside of ColdFusion. I could then instantiate this using a URL Class Loader and use it that way. At least that way I wouldn't be hurt by any other changes to the ColdFusion language.
Want to use code from this post? Check out the license.
Reader Comments
Hi Ben,
Really nice article. I for one, would really love to begin integrating Java into my ColdFusion apps, but I don't really know where to start from.
Are there any tutorials that show the integration of cf and java. Tutorials that show how to call simple methods in Java classes and have their results returnd to CF. Tutorials that show to create your own custom Java classes, tells you where to save them (class path) and how to call them in your CF article.
Thanks in anticipation of any response.
William,
I have been just piecing it together over time. You might want to start just by searching this site for "java". I like testing out this stuff. Once of the biggest things that I have come to love is the use of the Java regular expression. They are much faster (and more powerful) in Java than they are in ColdFusion. If you like regular expressions, you will love calling Java string methods directly from ColdFusion.
But as far as tutorials, sorry, I wish I knew. Please feel free to ask me any Java questions to run into (which I may or may not be able to help you with).
Thanks for your prompt response Ben.
I'll definitely get in touch with you if I run into any issues. Meanwhile, I'll be googling......
Ben,
Glad to see you having lots of fun with Java and CF.
The difference between a Class object and an Object object, is that a Class object is basically metadata for an actual class.
With it you can do all sorts of neat things, like ask it what methods you have on that class etc.
It falls into the world of 'Reflection'.
You'll have lots of fun there ;o)
On a side note - any reason you didn't just use the JavaLoader cfc? or was it a case of 'oooh.. how does this work'? (which I totally get)
Mark,
The reason I didn't just use JavaLoader.cfc is that I was trying to do it using the standard Url Class Loader. Partly because I didn't quite grasp what was going on in the JavaLoader.cfc and partly because I wasn't too hot on using the JavaProxy object. Once I pounded my head against the wall a bunch of times and pulled Dave over (my in house Java advice guy), we went through your code together. He hasn't done much of the way in reflection, but together we sort of figured that yours didn't create new instances of a class, but instead returned the class object (or something like that). So, mostly it was a misunderstanding and an attempt to do something that could not quite be done.
A question though, how does a static method figure into this. Like if I was to call something like Pattern.Compile() where Compile() is a static method of class Pattern, what is that? Is that a class? Is that an actual object? Like, how does that fit in with the "new" operator? What does new do? Just initialize it? Does the class exist before it is initialized? Or is it just a memory place holder at that point?
Sorry for all the questions, but that's about all I have at the moment :)
Ben! Woah! Lots of questions!
Well first off = what you've done there is actually pretty much a mimic of what JavaLoader does behind the scenes. Not that that is a bad thing, but is pretty much what it does.
http://www.compoundtheory.com/?action=displayPost&ID=114
This should explain much of the process to you.
http://www.leepoint.net/notes-java/flow/methods/50static-methods.html
Here is a pretty good description of static methods and properties - let me know if you have any issues.
Side note: lots of this stuff is much easier to explain via IM... send me an email with your IM details if you wanna chat.
Mark,
Thanks so much for the links. I will check these out ASAP!
Hi Ben (and maybe Mark :) ),
I just started using javaloader for the great combine.cfc that combines and minifies and then gzips all static js, css, etc to rapidly shrink the pages our application serves. If you are not using this, I strongly recommend as it is great!
I love the portability of the javaloader solution rather than putting class (.jar) files into the CF class path and am thinking of using it to update the POI jars for the POI tags as well.
My question is in regards to efficiency and server overhead. When you use javaloader, I see that it stores the classes in the Application scope. If there are 1000 or 10000 users logged in, then does this negatively impact memory or the server in anyways since the classes are loaded into each application scope. It seems that if you place the jar files in the classpath, then restart CF, CF loads the classes into memory and they are accessible to all users, which intuitively seems like much less server overhead.
The only thing I can think of is when you make a cfapplication call to start an app, it loads all the CF classes into the application scope anyways, so that there is no difference.
If you can shed any light onto this (or know where someone may have already), I would be most thankful as always for your bomber information!
Thanks,
Matt
I tried searching you site for Java and came out with 0 results. I keep getting an erro with com.compoundtheory.classloader.NetworkClassLoader. Any idea why it would be throwing a java.lang.ClassNotFoundException?
Yeah I know Its pretty late to reply on this post. But I just googled some error info and redirected me to this blog, where i found some matching discussions with my error problem.
I am using JavaLoader.cfc in my application (previous developer used it, i am new to this code). When Schedule job runs at night we encountered one error on daily basis :
"An exception occurred while instantiating a Java object. The class must not be an interface or an abstract class. Error: ''"
At line :
<cffunction name="createJavaProxyCFC" hint="create a javaproxy, dependent on CF server settings" access="private" returntype="any" output="false">
<cfargument name="class" hint="the java class to create the proxy with" type="any" required="Yes">
<cfscript>
return createObject("component", "JavaProxy")._init(arguments.class);
</cfscript>
</cffunction>.
I am having very limited knowledge, can anyone explain me why this error?
In fact the stack trace tells me that error is in this line :
<cffunction name="createJavaProxy" hint="create a javaproxy, dependent on CF server settings" access="private" returntype="any" output="false">
<cfargument name="class" hint="the java class to create the proxy with" type="any" required="Yes">
<cfscript>
return createObject("java", "coldfusion.runtime.java.JavaProxy").init(arguments.class);
</cfscript>
</cffunction>