AOP Docs: Understanding Aspects / Advice / Point-Cuts
Now that we've got some AOP basics out of the way, we're going to dive into the concept of Aspects which are commonly referred to as "Advice".
As stated previously, "Aspect" is standard AOP terminology which makes reference to an actual process or algorithm that affects other parts of the system - hence the abstract concept of a cross-cutting concern. An Aspect created within the context of the AOP API comes in the form of two different ColdFusion constructs; A stand-alone method reference, or a method implemented as a member of a .cfc.
It is the responsibility of an aspect to transparently "intercept" a call to a specific method on a specific object before the original method (point-cut) is invoked, control whether or not the original method is invoked at all, or handle method invocation post-processing. These responsibilities are determined with the position in which the aspect is applied - also known as a join-point.
Join Points
A join-point specifies exactly when an aspect takes control. The cfcommons AOP library supports three such positions;
- Before
- After
- Around
Aspects applied at the "Before" or "After" positions are structured very similarly - while aspects intended to intercept invocations "Around" a point-cut behave very different which we'll discuss very shortly.
Aspect Contract
We'll get into the nitty-gritty details of each type of Aspect in a moment, but what's important to understand up-front is that in both scenarios, an Aspect ultimately is a method (UDF) responsible for implementing some logic. Methods that are intended to be used as aspects must adhere to a specific contract - that is, they must follow a specific convention in order to behave in the intended manner. Aspect contracts vary based off of the join-point to which they are attached (before, after, around). The following contract applies to methods that will be placed at the "before" or "after" join points;
- Aspect methods must have a return type of struct.
- Aspect methods must return the original argument collection
Below is an example of what a "before" or "after" aspect method might look like;
Aspects whose intended join point is "around" behave much differently than the other two join-point position types. Around aspects can implement logic both before and after an intended point-cut within the same method implementation. The rules that apply to an "around" aspect are as follows;
- A point-cut can have one and only one aspect whose intended join-point is "around".
- It is the responsibility of the "around" aspect to invoke the intended point-cut method within its implementation
- If the point-cut method is expected to return a result, the "around" aspect must account for this as it's own return type.
Below is an example of what an "around" "aspect method might look like;
Flow Control
All aspects, regardless of their intended join-point, have access to the argument collection originally intended for the point-cut. For instance, if you have a method named doSomething(required string task) that is the intended point cut of an aspect - and you apply a cross-cutting concern (aspect) to that method invocation, then that aspect method can access the the value of "task" which was intended for the original doSomething() method via "arguments.task".
With that kind of access to the arguments and values intended for the original method invocation, any aspect that has been applied to that method with a join-point of "before" can not only access any argument values - but change them if necessary, even remove them entirely. This is why it's important, especially for aspects whose join-point is "before" a target invocation, for that aspect to return the original argument collection.
arguments.pointCutName
"pointCutName" is an argument that is provided to both "before" and "after" join-point aspects. This variable contains the name of the method that is being intercepted.
arguments.interceptedInstance
In the aspect contracts section, I touched very briefly on the contract for an "around" aspect. It's time to elaborate on the second contract point. "interceptedInstance" is another special variable that exists within the runtime argument collection for an intercepted method invocation. This argument only exists for aspects whose join point is "around". It contains a reference to the original object whose method is being intercepted by an aspect. This coincides with the special behavior that "around" aspects exhibit - which is different than "before" or "after" aspects. It is the responsibility of the "around" aspect to invoke the target point-cut method on the "interceptedInstance" object reference. Otherwise it will never occur. Below is an example of a possible "around" advice;
Understanding the "around" Order of Precedence
When a target point-cut method has multiple "before" or "after" aspects applied to it, the order of precidence for aspect execution is simple - the aspects are executed in the order in which they were applied. This is not the case for an "around" aspect and could probably stand a thorough explaination.
Take the following pseudo-code as an example;
Order of precedence is as follows;
Let's thrown an "around" aspect in there now;
Order of precedence is now;
Abstracting Point-cut Details from "around" advice
As stated earlier, advice of type around is responsible for invoking the target point-cut method on the original target object instance. This is to ensure the around advice can perform both pre and post-processing logic "around" the target invocation. In our original example of around advice, we were referencing the actual point-cut method by name in the implementation of the advice as well as it's only argument - again, by name. This is fine and will work as expected, but isn't very flexible. First off, it tightly couples the advice implementation to the method signature of the interceptedInstance point-cut. Which means that if ever the method signature were to change, either it's name or it's named arguments - then our advice might behave erratically at best, or simply break alltogether. Second of all, with that level of coupling, it would be very difficult to apply that advice to any other point-cuts as they would have to have the same exact method signature, which is unlikely and as I stated before, very inflexible and antithetical to AOP in general.
Thankfully there's a way built into the AOP plumbing to ensure much more loose coupling than that of our original example. It is through the use of both the interceptedInstance and pointCutName arguments that are automatically provided to the around advice method at runtime. Using these two arguments, along with the very flexible native CF API, we can ensure that no reference whatsoever exists to named parts of the original point-cut;
The above code performs the same exact function as the original example except now you can easily apply this aspect to many different point-cuts. The above code could also very easily be translated into tags with the use of the <cfinvoke /> tag in stead of the evaluate() call.