Skip to main content
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Andrew Duvall
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Andrew Duvall

Publishing Software Libraries With An Optional External Class Loader

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel