Skip to main content
Ben Nadel
It's not enough; but, not enough is better than nothing.
Ben Nadel at RIA Unleashed (Nov. 2010) with: David Crouch
Ben Nadel at RIA Unleashed (Nov. 2010) with: David Crouch@ten24web )

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