Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Publishing Software Libraries With An Optional External Class Loader

By Ben Nadel on
Tags: ColdFusion

This morning, I was going to tinker with creating a ColdFusion component wrapper for the parts of the Google Closure compiler that I looked at over the weekend. But, when I sat down to code, I realized that I had an interesting dilemma on my hands. The code from my previous blog post was written using ColdFusion 10; here at the office, however, I'm still on ColdFusion 9. So, how do I instantiate the Java classes provided in the compiler.jar archive?

At home, it was easy - I just added the JAR file as a per-application setting (a feature of ColdFusion 10) and used createObject(); at the office, however, I would have to either use the JavaLoader or add the JAR file to my ColdFusion class paths. But, this thinking lead me to another question - once I chose an approach, how should I build that decision into my software library?

As I was considering my options, I remembered something that I had read in Practical Object-Oriented Design in Ruby, by Sandi Metz. In her chapter on "Managing Dependencies," Metz suggested isolating the creation of new instances that could not be injected into your object:

If you are so constrained that you cannot change the code to inject a Wheel into a Gear, you should isolate the creation of a new Wheel inside the Gear class. The intent is to explicitly expose the dependency while reducing its reach into your class. Practical Object-Oriented Design in Ruby (p. 42).

This sounded like the perfect plan! I could isolate the creation of the Closure compiler classes within private methods; then, I could create a sub-class of my ColdFusion component wrapper that overrides the instance-creation methods, using a Class Loader instead of the native createObjet() function.

To experiment with this approach, I created a partial ColdFusion wrapper for the Java class, HashSet. In the following code, notice that the actual instantiation of the HashSet class is isolated within its own method:

  • <cfscript>
  •  
  • component
  • output = false
  • hint = "I provide an interface to some Java library (ex, HashSet)."
  • {
  •  
  •  
  • // Return an initialized wrapper for the Java HashSet collection.
  • public any function init( array initialItems = [] ) {
  •  
  • collection = createCollection();
  •  
  • addAll( initialItems );
  •  
  • return( this );
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I add all of the given items to the collection. Is chainable.
  • public any function addAll( required array items ) {
  •  
  • collection.addAll( items );
  •  
  • return( this );
  •  
  • }
  •  
  •  
  • // I return the hashSet as a ColdFusion array.
  • public array function toArray() {
  •  
  • var result = [];
  •  
  • result.addAll( collection );
  •  
  • return( result );
  •  
  • }
  •  
  •  
  • // ... and many more methods ...
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I instantiate and return the Java collection. This create
  • // action is inside a private method so that it can be overridden
  • // in situations that may require a class-loader.
  • private any function createCollection() {
  •  
  • return(
  • createObject( "java", "java.util.HashSet" ).init()
  • );
  •  
  • }
  •  
  •  
  • }
  •  
  • </cfscript>

With the creation of the HashSet encapsulated within its own method, I can now create a sub-class component to override the way in which the Java object is created:

  • <cfscript>
  •  
  • component
  • extends = "HashSet"
  • output = false
  • hint = "I extend the core library, but provide alternate CREATE methods."
  • {
  •  
  •  
  • // Return an initialized wrapper for the Java HashSet collection.
  • public any function init(
  • required any classLoader,
  • array initialItems = []
  • ) {
  •  
  • loader = classLoader;
  •  
  • return(
  • super.init( initialItems )
  • );
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I instantiate and return the Java collection using the given
  • // class loader.
  • // --
  • // NOTE: I am using a VECTOR here instead of a HASHSET so that I
  • // can see a difference in the output - Vector produces ordered
  • // output; hashset does not.
  • private any function createCollection() {
  •  
  • return(
  • loader.createInstance( "java.util.Vector" ).init()
  • );
  •  
  • }
  •  
  •  
  • }
  •  
  • </cfscript>

Here, the ColdFusion component constructor takes an additional argument - the external class loader. Then, the sub-class overrides the createCollection() method, using the external class loader to create the HashSet instance.

You may have noticed that my sub-class isn't actually creating a HashSet; instead, it's creating a Vector object. I did this so that I could see a difference in my testing output. Both of these Java classes implement the Collection interface; but, the Vector class is ordered whereas the HashSet class is not ordered (or, at least you cannot depend on its ordering).

Once I had these two classes, I then created two HashSet wrappers, one that relied on the native createObject() function and one that relied on an external class loader:

  • <cfscript>
  •  
  •  
  • // Let the HashSet use the native class loader.
  • hashSet = new lib.HashSet( [ 1, 2, 3 ] );
  •  
  •  
  • writeOutput( "Native Class Loader <br />" );
  •  
  • writeOutput( arrayToList( hashSet.toArray(), ", " ) );
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  • writeOutput( "<br />" );
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // This time, create a HashSet that will defer to this custom
  • // loader when it needs to create Java objects that rely on
  • // external JAR files.
  • loader = new lib.ClassLoader();
  •  
  • altHashSet = new lib.HashSetWithLoader( loader, [ 4, 5, 6 ] );
  •  
  •  
  • writeOutput( "Custom Class Loader <br />" );
  •  
  • writeOutput( arrayToList( altHashSet.toArray(), ", " ) );
  •  
  •  
  • </cfscript>

When I run the above code, I get the following output:

Native Class Loader
3, 2, 1
Custom Class Loader
4, 5, 6

Notice that the sub-class output is in order, "4, 5, 6," whereas the primary class output is in reverse order, "3, 2, 1." This indicates that the sub-class was, indeed, overriding the createCollection() method, deferring to the external class loader.

I really like this approach! It allows me to build software libraries that depend on some assumptions; but, that are flexible enough to allow those assumptions to be overridden by sub-classes in special use-cases.




Reader Comments

@All,

I just put this practice into affect in my ClosureCompiler.cfc facade to the Google Closure compiler:

http://www.bennadel.com/blog/2518-ClosureCompiler-cfc-A-ColdFusion-Facade-For-Google-s-Closure-Compiler.htm

Since the library depends on the compiler.jar file, it can either use the core ColdFusion class paths; or, you can sub-class the ClosureCompiler.cfc and override the methods that create and initialize the underlying Java objects.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.