Lightwire Tutorial 2: AOP
Monday, March 15th, 2010

Now that I’ve gone over the basics of Lightwire as far as creating bean and injecting dependencies go, let’s get into some Aspect Oriented Programming (AOP). AOP is about addressing the separation of cross-cutting concerns from the main application functionality. Yeah, I know, the first time I read this, it came through as “blah blah blah [jargon here] blah blah blah.” So to simplify, cross-cutting concern just means some functionality that can apply to many parts of an application. The examples you’ll likely see over and over for this (and rightly so) are logging and security. Suppose you have a bunch of components/beans and you want to log method calls for several/all the methods in those beans. You could do something like this in a method:

<!--- Logger.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfreturn this />
	</cffunction>

	<cffunction name="log">
		<cfargument name="txt" type="string">

		<!--- Some code to write the log text to a file --->
		.....
	</cffunction>
</cfcomponent>

<!--- Controller.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfset variables.Logger = createObject("component", "Logger") />

		<cfreturn this />
	</cffunction>

	<cffunction name="doSomething">
		<cfset variables.Logger.log("Function doSomething() ran.") />

		<!--- A bunch of code here doing amazing things! --->
		.....
	</cffunction>
</cfcomponent>

Now think of having to do it for 75 beans with an average of 7 methods in each. Are you feeling the pain? Or what if you suddenly needed to apply security to certain parts of a site running on an MVC framework. Imagine your controller:

<!--- Security.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfset variables.Security = createObject("component", "Security") />

		<cfreturn this />
	</cffunction>

	<cffunction name="hasSuperExtraMegaSecretClearance">
		<!--- Some code to test if the user is the director of the CIA --->
		.....
	</cffunction>
</cfcomponent>

<!--- Controller.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfreturn this />
	</cffunction>

	<cffunction name="uncoverKennedyAssassinationPlot">
		<cfif variables.Security.hasSuperExtraMegaSecretClearance()>
			<!--- Some code to load all that stuff from the databases at Langley --->
			.....
		</cfif>
	</cffunction>
</cfcomponent>

Now think about adding that cfif for all the methods that pertain to Area 51. Sounds like a great way to lose your sanity. Enter AOP! AOP will allow you to do all these things and avoid that stream of curses that would undoubtedly result from your boss telling you “Listen Bob, I have this project I need you to work on…” For instance, let’s take a look at the first scenario using Lightwire and AOP. In the code below, assume that the Logger.cfc file is exactly the same as the one above.

<!--- Controller.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfreturn this />
	</cffunction>

	<cffunction name="doSomething">
		<!--- A bunch of code here doing amazing things! --->
		.....
	</cffunction>
</cfcomponent>

<!--- LogAdvice.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfargument name="Logger" type="any">

		<cfset variables.Logger = arguments.Logger />

		<cfreturn this />
	</cffunction>

	<cffunction name="before" returntype="string">
		<cfargument name="method" type="string">
		<cfargument name="args" type="struct">
		<cfargument name="target" type="any">

		<cfset variables.Logger.log("Function #arguments.method#() ran.") />
	</cffunction>
</cfcomponent>

<!--- BeanConfig.cfc --->
<cfcomponent extends="lightwire.BaseConfigObject" hint="A LightWire configuration bean.">
	<cffunction name="init" output="false" returntype="any" hint="I initialize the config bean.">
		<cfscript>
		// Call the base init() method to set sensible defaults. Do NOT remove this.
		Super.init();

		// OPTIONAL: Set lazy loading: true or false. If true, Singletons will only be created when requested. If false, they will all be created when
		//LightWire is first initialized. Default if you don't set: LazyLoad = true.
		setLazyLoad("false");

		// BEAN DEFINITIONS (see top of bean for instructions)
		addSingleton("cfcPath.Controller");
		addSingleton("cfcPath.Logger");
		addSingleton("cfcPath.LogAdvice");

		// Depency Injection
		addConstructorDependency("LogAdvice", "Logger");

		// AOP Advice
		addBeforeAdvice("Controller", "Logger", "doSomething");
		</cfscript>

		<cfreturn this />
	</cffunction>
</cfcomponent>

As you can see, the logging code has been completely removed from the original Controller.cfc file. The Lightwire config file is fairly straightforward. The Controller bean, Logger bean, and a new LoggerAdvice bean get defined with addSingleton() calls. Once defined, a Before advice is used to tell Lightwire that you want method “before” (it’s the default method for Before advice) from bean “Logger” to run before the method “doSomething” in bean “Controller” when it is called. Simple, right? For this one case, it can seem like overkill, but if you have a lot of logging to do, it’ll save you a lot of time. Note that if you wanted to log every function call in the Controller bean, you could just put “*” as the third argument instead of “doSomething”, and you can also ue a comma-delimited list of functions. If there were more beans you needed to do logging for, you would just need to add an addBeforeAdvice() call for that other bean. This may seem like a problem if you have a lot of beans, but thanks to the programmatic nature of the Lightwire configuration, it’s usually easy to loop through all the CFCs in a directory, add a singleton for each, and add the advice for each. Also, while I think the LoggerAdvice bean is recommended in this case to keep the generic Logger functionality (you may be using it for other things than logging method calls), you could technically only have a Logger bean and define a before() method in it, eliminating the need for the LoggerAdvice.

Now, let’s move on to the second example. Here’s how you could implement the security using AOP and an Around advice:

<!--- Controller.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfreturn this />
	</cffunction>

	<cffunction name="uncoverKennedyAssassinationPlot">
		<!--- Some code to load all that stuff from the databases at Langley --->
		.....
	</cffunction>
</cfcomponent>

<!--- SecurityAdvice.cfc --->
<cfcomponent>
	<cffunction name="init">
		<cfargument name="Security" type="any">

		<cfset variables.Security = arguments.Security />

		<cfreturn this />
	</cffunction>

	<cffunction name="around" returntype="string">
		<cfargument name="AdviceDispatcher" type="any">

		<cfif variables.Security.hasSuperExtraMegaSecretClearance()>
			<cfset arguments.AdviceDispatcher.run()>
		</cfif>
	</cffunction>
</cfcomponent>

<!--- BeanConfig.cfc --->
<cfcomponent extends="lightwire.BaseConfigObject" hint="A LightWire configuration bean.">
	<cffunction name="init" output="false" returntype="any" hint="I initialize the config bean.">
		<cfscript>
		// Call the base init() method to set sensible defaults. Do NOT remove this.
		Super.init();

		// OPTIONAL: Set lazy loading: true or false. If true, Singletons will only be created when requested. If false, they will all be created when
		//LightWire is first initialized. Default if you don't set: LazyLoad = true.
		setLazyLoad("false");

		// BEAN DEFINITIONS (see top of bean for instructions)
		addSingleton("cfcPath.Controller");
		addSingleton("cfcPath.Security");
		addSingleton("cfcPath.SecurityAdvice");

		// Depency Injection
		addConstructorDependency("SecurityAdvice", "Security");

		// AOP Advice
		addAroundAdvice("Controller", "Security", "uncoverKennedyAssassinationPlot");
		</cfscript>

		<cfreturn this />
	</cffunction>
</cfcomponent>

This case is similar to the first, except this time we’re using an Around advice. Around advice gives you more complete control over method execution, it’s kind of a Before and After advice rolled into one. The AdviceDispatcher argument it receives allows you to retrieve info about the method and bean that is being advised, as well as control the timing of the execution of the original method. You could even decide, depending on logic, that you don’t want to execute that method at all. And again, if you need to apply security to many different parts of your app, it’s easy to use this and apply it to multiple beans/method in a generic way.

As a side note, it’s worth mentioning that while I use Lightwire because it’s the project I’m contributing to, all this is applicable to ColdSpring, which offers the same types of advice. The difference is that you’d have to define your beans, dependencies, and advices in XML and if I remember correctly your beans have to extend certain components. But it’ll get the same job done. Also, it’s worth noting that AOP is not yet in the main distribution of Lightwire (see my previous post on AOP in Lightwire), it’s only available here until it’s further vetted. Feel free to download it from the Download section and give it a try. Any feedback is very appreciated.

I hope that from these examples you’re able to see both the power and convenience that AOP can provide you with, and how to use Lightwire to achieve those results.

Leave a Reply