Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
In computing, aspect-oriented programming (AOP) is a programming paradigm which aims to increase modularity by allowing the separation of cross-cutting concerns. AOP forms a basis for aspect-oriented software development.
I won't go into incredible software theory but pragmatic examples. What is the value of AOP? What does it solve? Well, how many times have we needed to do things like this:
As you can see from the example above, my real business logic is in the //do some work here
comment, but our code is littered with logging and transactions. What if I have 10 methods that are very familiarly the same? Do I repeat this same littered code (cross-cutting concerns)? The answer for most of us has always been "YES! Of Course! How else do I do this?".
Well, you don't have to, AOP to the rescue. What AOP will let you do is abstract all that logging and transaction code to another object usually called an Aspect. Then we need to apply this aspect to something right? Well, in our case it has to apply to a target object, our UserService
, and to a specific method, save()
, which is usually refered to as the join point.
What does applying an aspect mean? It means that we will take that aspect you write and execute it at different points in time during the execution of the method you want to apply it to. This is usually refer to as an advice, "Hey Buddy! Run it here!!!".
There are multiple types of AOP advices like before, around, after, etc. We have seen in ColdBox event handlers that you can do a preHandler, postHandler and and aroundHandler
methods. These are AOP advices localized to event handlers that let you execute code before a handler event, after a handler event, or completely around the handler event. The most powerful form of advice is around, as it allows you to completely surround a method call with your own custom code. Does it ring a bell now? The transaction code for Pete's Sake! You need it to completely surround the method call! Voila! The around advice will allow you to completely take over the execution and you can even determine if you want to continue the execution or not.
So how does this magic happen? Well, our WireBox AOP engine will hijack your method (join point) and replace it with a new one, usually called an AOP proxy. This new method has all the plumbing already to allow you to apply as many aspects you like to that specific method. So as you can see from our diagram below, the save method is now decorated with our two aspects, but for all intent and purposes the outside world does not care about it, they just see the save()
method.
WireBox fully supports aspect-oriented programming (AOP) for ColdFusion (CFML) and any ColdFusion framework. Just note the different namespaces if using within the ColdBox Platform and standalone WireBox.
Our WireBox AOP RefCard will get you up and running in no time.
ColdFusion 11+
Lucee 4.5+
AND
Disk/Memory Generation
WireBox has an amazing event driven architecture that can help you modify, listen and do all kinds of magic during object creation, wiring, etc. Our AOP implementation is just a listener (wirebox.system.aop.Mixer
) that will transform objects once they are finalized with dependency injection. This means, our AOP engine is completely decoupled from the internals of the DI engine and is incredibly fast and light weight.
So let's activate it in our WireBox binder configuration:
That's it! That tells WireBox to register the AOP engine once it loads. This listener also has some properties that you can tweak:
Aspect: A modularization of a concern that cuts across multiple objects.
Target Object : The object that will be applied with Aspects across certain methods or join points.
Join Point : A point of execution in a target object that will be applied a specific aspect to it. This is usually the execution of a method.
Advice : An action taken at a particular join point. Usually, before, after or around it.
AOP Proxy : An object or method representation for the original join point or method.
WireBox has an amazing event driven architecture that can help you modify, listen and do all kinds of magic during object creation, wiring, etc. Our AOP implementation is just a listener that will transform objects once they are finalized with dependency injection. This means, our AOP engine is completely decoupled from the interals of the DI engine and is incredibly fast and light weight. So let's activate it in our WireBox binder configuration:
That's it! That tells WireBox to register the AOP engine once it loads. This listener also has some properties that you can tweak:
Now that we have activated the AOP engine, let's build a simple method logger aspect that will intercept before our method is called and after our method is called. So if you remember your AOP dictionary terms, we will create an aspect that does a before and after advice on the method. Phew! To do this we must implement a CFC that WireBox AOP gives you as a template: wirebox.system.aop.MethodInterceptor
. This CFC interface looks like this:
This means, that we must create a CFC that implements the invokeMethod
method with our own custom code. It also receives 1 argument called invocation
that maps to a CFC called wirebox.system.aop.MethodInvocation
that you can learn from our cool .
Our approach to AOP is simplicity, therefore this invokeMethod
implements the most powerful advice called around advice, so you will always do an around advice, but it will be up to your custom code to decide what it does before (beforeAdvice), around (aroundAdvice) and after (afterAdvice) the method call.
The other advantage of WireBox AOP aspects is that once they are registered with WireBox they act just like normal DI objects in WireBox, therefore you can apply any type of dependency injection to them.
Here are a list of the most useful methods in this CFC
getMethod()
: Get the name of the method (join point) that we are proxying and is being executed
getMethodMetadata()
: Get the metadata structure of the current executing method. A great way to check for annotations on the method (join point)
getArgs()
: Get the argument collection of the method call
setArgs()
: Override the argument collection of the method call
getTarget()
: Get the object reference to the target object
getTargetName()
: Get the name of the target object
getTargetMapping()
: Get the object mapping of the proxied object. Great for getting metadata information about the object, extra attributes, etc.
proceed()
: This is where the magic happens, this method tells WireBox AOP to continue to execute the target method. If you do not call this in your aspect, then the proxyied method will NOT be executed.
The last method is the most important one as it tells WireBox AOP to continue executing either more aspects, the proxyed method or nothing at all.
Since we have now mapped our aspect in WireBox, we now need to tell it the most important things:
To what classes or CFCs should we apply this aspect to?
To what methods or join points should we apply this aspect to?
We do this with another binder DSL method:
bindAspect(classes,methods,aspects)
What is up with that funky match()
.... stuff? Well, the classes and methods arguments of the bindAspect()
call uses our AOP funky matcher methods that exist in an object called: wirebox.system.aop.Matcher
. This matcher object is used to match classes and methods to whatever criteria you like. The binder has a convenience method called match()
that creates a new matcher object for you and returns it so you can configure it for classes and method matching. Here are the most common matching methods below:
WOW! We can really pin point anything in our system! So now that we have binded our aspect to our UserService I can rewrite it to this:
Now isn't that pretty? Much nicer and compact hugh! Plus I can reuse the method logger for ANY method in ANY class I desire (Muaaaahaaaa), that's an evil laugh by the way! But we are not done, let's keep going and do the Transactional aspect.
Here is my MethodLogger aspect that I will create:
You can see that I do some DI via annotations:
A normal constructor with one optional argument for logging results:
And our invokeMethod implementation:
As you can see, the before advice part is what happens before the execution of the real method (or more aspects) occurrs. So everything before the call to arguments.invocation.proceed()
:
Then we execute the real method or more aspects (we do not do anything around the method call):
Finally, we do the after advice part which happens after the method or other aspects fire and results are returned:
That's it. I have succesfully created an aspect. What's next!
WireBox AOP supports the concept of self aspect bindings. This means that we can make things even simpler by letting the Aspect you build control what class and methods it will match against. This is great, one less thing to remember when developing the code. So let's start with the code first:
That's it! Our transaction aspect is done and it will also bind itself to ANY class and ANY method that has the transactional annotation. Then you just map it:
We are done now, by mapping the aspect WireBox detects the two annotations classMatcher
and methodMatcher
and binds it for you. WOW, but where did you get that from? Well, the component has two annotations:
How cool is that! My aspect can determine the matching for me already.
One thing to note about self binding aspects is that you can also override their matching by using the autoBind
argument in the mapAspect()
method call. So if you wanted to override the class and method matching on this aspect you would do this:
Create a classMatcher
annotation on the component with the following DSL values:
Now that we have built our aspect we need to register it with WireBox so it knows about it and all DI can be performed in it. Let's open our WireBox binder and use the following DSL method:
mapAspect(aspect,autoBinding=[true])
This tells WireBox to register a new aspect called MethodLogger
that points to the CFC model.aspects.MethodLogger
that I have just built. WireBox will then mark that object as an aspect, create it once the injector loads and have it ready for building.
Property
Type
Required
Default
Description
generationPath
cf include path
false
/wirebox/system/aop/tmp
The location where UDF stubs will be generated to. This can be to disk or memory.
classMatchReload
boolean
false
false
A cool flag to allow you to reload the class matching dictionary for development purposes only.
Property
Type
Required
Default Value
Description
generationPath
cf include path
false
/wirebox/system/aop/tmp
The location where UDF stubs will be generated to. This can be to disk or memory.
classMatchReload
boolean
false
false
A cool flag to allow you to reload the class matching dictionary for development purposes only.
DSL | Description |
any | Matches against any class path or method name |
annotatedWith:{annotation} | Matches against the finding of an annotation in a cfcomponent |
annotatedWith:{annotation}:{value} | Matches against the finding of an annotation value in a cfcomponent |
mappings:{mappings} | Matches to ONLY the named mapping(s) you pass to this method as a list or array. |
instanceOf:{classPath} | Matches if the target object is an instance of the classPath. This internally uses the ColdFusion isInstanceOf() method. |
regex:{regex} | Matches against a CFC instantiation path or function name using regular expressions |
Method | Class Matching | Method Matching | Description |
any() | true | true | Matches against any class path or method name |
returns(type) | false | true | Matches against the return type of methods |
annotatedWith(annotation,[value]) | true | true | Matches against the finding of an annotation in a cfcomponent or cffuntion or matches against the finding AND value of the annotation. |
mappings(mappings) | true | false | Matches to ONLY the named mapping(s) you pass to this method as a list or array. |
instanceOf(classPath) | true | false | Matches if the target object is an instance of the classPath. This internally uses the ColdFusion isInstanceOf() method |
regex(regex) | true | true | Matches against a CFC instantiation path or function name using regular expressions |
methods(methods) | false | true | Matches against a list of explicit method names as a list or array |
andMatch(matcher) | true | true | Does an AND evaluation with the current matcher and the one you pass in the method. |
orMatch(matcher) | true | true | Does an OR evaluation with the current matcher and the one you pass in the method. |
So now that we mapped our self binding aspect, let's clean up our code further:
Now isn't this amazing! We are back to our original business logic and code with no extra fluff around logging, transactions and pretty much just code noise. Now we are talking and cooking with AOP. So hopefully by now you nave a good grasp of what AOP does for you and how to implement it in WireBox. So here are the steps to remind you:
Create an aspect that implements: wirebox.system.aop.MethodInterceptor
Map the aspect in your WireBox binder via the mapAspect()
method
Bind the aspect to classes and methods via annotations or the bindAspect()
method
So, easy as 1-2-3! AOP is powerful and extermely flexible. Hopefully, your imagination is now going into overdrive and coming up with ways to not only clean up your code from cross cutting concerns, but also help you do things like:
threaded methods
transform method results
method auditing
method security
cache method results
etc.
This aspect is a self-binding aspect that will surround methods with native Hibernate transaction blocks.
To use, just declare in your binder, overriding the self-binding is totally optional.
Create a methodMatcher
annotation on the component with the following DSL values:
WireBox comes bundled with three aspects that you can use in your applications and can be found in wirebox.system.aop.aspects
:
This aspect enables you to log method calls and their results. You will have to bind it to the methods you want it to listen to. It has one constructor argument:
This aspect is a self-binding aspect that will surround methods with simple ColdFusion transaction blocks.
To use, just declare in your binder, overriding the self-binding is totally optional.
ClassMatcher
MethodMatcher
any
annotatedWith:transactional
DSL
Description
any
Matches against any class path or method name
annotatedWith:{annotation}
Matches against the finding of an annotation in a cfcomponent
annotatedWith:{annotation}:{value}
Matches against the finding of an annotation value in a cfcomponent
returns:{type}
Matches to ONLY the methods that return the {type}
methods:{methods}
Matches to ONLY the named methods(s) you pass to this method as a list or array.
regex:{regex}
Matches against a CFC instantiation path or function name using regular expressions
Aspect
Description
CFTransaction
A simple ColdFusion transaction Aspect for WireBox
HibernateTransaction
A cool annotation based Transaction Aspect for WireBox
MethodLogger
A simple interceptor that logs method calls and their results
Name
Type
Required
Default
Description
logResults
boolean
false
true
Whether to log the results of method calls or not.
ClassMatcher
MethodMatcher
any
annotatedWith:transactional