An Introduction to Aspect-Oriented programming with JBoss AOP
Logging as a Cross-Cutting Concern
One of the main uses of AOP is to extract out cross-cutting concerns. Say we wanted to add some logging whenever an object is created, when its fields are set, and when its methods are called. The “obvious” way to do that in our example would be to go in and add logging statements to various points of our application. The problem with this approach is that there might be lots of places to edit, scattered around our system, so we would need to identify those places, make our changes, and recompile our application. To turn this behaviour off, we would need to remove our logging statements again and recompile our code again. So the “obvious” way both leads to bloat, and is difficult to turn on and off.
Using AOP we can encapsulate this cross-cutting code in an aspect. The aspect itself is just a normal class, which can hold state, extend other classes, everything you can do in a normal Java class.
package bank;
import org.jboss.aop.joinpoint.ConstructorInvocation;
import org.jboss.aop.joinpoint.FieldWriteInvocation;
import org.jboss.aop.joinpoint.MethodInvocation;
public class LoggingAspect
{
public Object log(ConstructorInvocation invocation) throws Throwable
{
try
{
System.out.println("C: Creating BankAccount using constructor " + invocation.getConstructor());
System.out.println("C: Account number: " + invocation.getArguments()[0]);
return invocation.invokeNext();
}
finally
{
System.out.println("C: Done");
}
}
public Object log(MethodInvocation invocation) throws Throwable
{
try
{
System.out.println("M: Calling method " + invocation.getMethod().getName());
System.out.println("M: Amount " + invocation.getArguments()[0]);
return invocation.invokeNext();
}
finally
{
System.out.println("M: Done");
}
}
public Object log(FieldWriteInvocation invocation) throws Throwable
{
BankAccount account = (BankAccount)invocation.getTargetObject();
System.out.println("F: setting field " + invocation.getField().getName() + " for BankAccount " + account.getAccountNumber());
System.out.println("F: Field old value " + account.getBalance());
System.out.println("F: New value will be " + invocation.getValue());
try
{
return invocation.invokeNext();
}
finally
{
System.out.println("F: Field new value " + account.getBalance());
System.out.println("F: Done");
}
}
}
The actual code implementing the cross-cutting concerns is encapsulated in methods called advice methods. For the type of advice methods that we are looking at now, around advice, the signature and format of the advice method is as shown here:
public Object (org.jboss.aop.joinpoint.Invocation invocation) throws Throwable
{
//Do something before
try
{
return invocation.invokeNext();
}
finally
{
//Do something after
}
}
The invocation parameter can be of type org.jboss.aop.joinpoint.Invocation, or one of its subclasses. Some of these are shown in the LoggingAspect, and which of the overloaded log methods gets called depends on what is being called once we apply our aspect. In the LoggingAspect we are able to handle calls to an object's constructor (the first log() method, starting on line 9), calls to an object's methods (the second log() method, starting on line 23) and calls to set a field (the third log() method, starting on line 37). These “events” are called joinpoints in AOP terminology. The invocation contains information about what field/method/constructor is being called. It also allows access to any arguments, and the object on which we are making the call. The around advice methods also contain a call to Invocation.invokeNext(), which propogates the call chain. If there are several advice methods applied to the target joinpoint, this call invokes the next advice in the chain. If this is the last advice in the chain, or as in our example, we are the only advice in the chain, the target joinpoint is called when we call Invocation.invokeNext().
To apply our aspects, we need some configuration to declare our aspects, and to select the joinpoints in our application where the advice methods should be applied. In JBoss AOP the easiest way to do this is with an xml configuration file, typically called jboss-aop.xml. First we declare our aspect:
<aop>
<aspect class="bank.LoggingAspect"/>
A binding has a pointcut to choose which methods we should apply the advice to. In this case we have a constructor expression that selects the constructor of the bank.BankAccount class that has an int parameter. Note that all class names used in pointcut expressions must be fully qualified. The word new is a special identifier within the pointcut language to pick out a constructor. Next, the around part of the binding says that we should apply the log advice from the bank.LoggingAspect to the joinpoints matched by its pointcut. In other words, when we call BankAccount's constructor, the first LoggingAspect.log() method, which takes a ConstructorInvocation, is invoked.
<bind pointcut="execution(bank.BankAccount->new(int))">
<around aspect="bank.LoggingAspect" name="log"/>
</bind>
The previous binding's pointcut only captures one joinpoint. The next one uses a wildcard in place of the method name, so we pick out all methods returning void that take an int parameter, regardless of their name. It looks a lot like the constructor expression in the previous binding, but also contains the return type of the methods we are interested in. This pointcut picks out BankAccount.debit() and BankAccount.credit(), and invokes the second LoggingAspect.log() method, which takes a MethodInvocation, when these methods are called.
<bind pointcut="execution(void bank.BankAccount->*(int))">
<around aspect="bank.LoggingAspect" name="log"/>
</bind>
Finally, we have a binding capturing writes to the balance field of the BankAccount class. In this case we are using a wildcard in place of the type's name, and the third LoggingAspect.log method, which takes a FieldWriteInvocation, will be invoked when the field's value is set:
<bind pointcut="set(* bank.BankAccount->balance)">
<around aspect="bank.LoggingAspect" name="log"/>
</bind>
In addition to wildcards, you can also capture whole inheritance hierarchies of classes, use typedefs for complex class expressions, all classes belonging to a package, and as we will see use annotations to capture a wide range of jonpoints.
To run this application with aop enabled, you need to pass in a few extra parameters into the jvm when starting it up, as we can see in the example's build.xml
<target name="run-load-time" depends="compile">The jboss.aop.path parameter contains the path to the jboss-aop.xml that declares our aspects and binds advice methods to joinpoints. The -javaagent switch points to the JBoss AOP library, which in turn turns on load-time weaving. When a class is first loaded, JBoss AOP will intercept that event and modify the bytecode of the class to add the hooks required to trigger the aspects for our selected joinpoints. In addition to weaving at load-time, you can weave the classes using our aopc post-processor. An example of this is shown in the example's build.xml. Running the example with load-time weaving yields the following output, now with quite a lot of extra information coming from our logging aspect:
<java fork="yes" failOnError="true" className="bank.Bank">
<sysproperty key="jboss.aop.path" value="jboss-aop.xml"/>
<jvmarg value="-javaagent:../libraries/jboss-aop-jdk50-single.jar"/>
<classpath refid="classpath"/>
</java>
</target>
*** Creating account 1
C: Creating BankAccount using constructor public bank.BankAccount(int)
C: Account number: 1
*** Bank Account constructor
C: Done
M: Calling method credit
M: Amount 150
*** BankAccount.credit()
F: setting field balance for BankAccount 1
F: Field old value 0
F: New value will be 150
F: Field new value 150
F: Done
M: Done
*** Creating account 2
C: Creating BankAccount using constructor public bank.BankAccount(int)
C: Account number: 2
*** Bank Account constructor
C: Done
M: Calling method credit
M: Amount 230
*** BankAccount.credit()
F: setting field balance for BankAccount 2
F: Field old value 0
F: New value will be 230
F: Field new value 230
F: Done
M: Done
*** Balance acount 1: 150
*** Balance acount 2: 230
*** Transfer 50 from account 1 to account 2
M: Calling method debit
M: Amount 50
*** BankAccount.debit()
F: setting field balance for BankAccount 1
F: Field old value 150
F: New value will be 100
F: Field new value 100
F: Done
M: Done
M: Calling method credit
M: Amount 50
*** BankAccount.credit()
F: setting field balance for BankAccount 2
F: Field old value 230
F: New value will be 280
F: Field new value 280
F: Done
M: Done
*** Balance acount 1: 100
*** Balance acount 2: 280
The code for this example can be found in the listing2/ folder of the download bundle.
| Attachment | Size |
|---|---|
| jboss-aop-samples.zip | 4.76 MB |
- Login or register to post comments
- 21935 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)










Comments
Joshua Partogi replied on Fri, 2008/08/29 - 10:05pm
jaikiran replied on Sat, 2008/08/30 - 3:52am
Good article, Kabir.
I have a question. Is the aspect code allowed to change the value that is being passed to the joinpoint? I mean, if the credi(int amount) method was being passed a value of 200, can the code in the aspect change this value to 100 before the credit method is invoked? If yes, is there any way to secure such access?
Kabir Khan replied on Mon, 2008/09/01 - 5:06am
Joshua,
Yes annotations can be used instead.An example can be found here and the containing directory contains more examples. If using annotations, instead of passing in the path of a jboss-aop.xml file with -Djboss.aop.path, you point JBoss AOP to a directory containing your annotated aspects using -Djboss.aop.class.path. The tutorial examples that come with the download will show how in more detail.
Kabir Khan replied on Mon, 2008/09/01 - 5:13am
Jaikiran,
The aspect code can modify the values that are passed in. There is currently no way to stop this, if that is what you mean by securing it. If this feature is important to you please ask for it on our user forums, and we will take it into consideration.
gsowji replied on Tue, 2009/02/10 - 11:37pm
gsowji replied on Wed, 2009/02/11 - 1:18am
daveeeed replied on Fri, 2009/06/05 - 2:54am
warrenty replied on Mon, 2009/06/08 - 1:40am
modthoa replied on Wed, 2009/06/17 - 10:51am
emad964 replied on Mon, 2009/06/29 - 4:14pm
jiji530 replied on Mon, 2009/06/29 - 9:42pm
wikaniko replied on Thu, 2009/07/09 - 1:02pm
superpan3721 replied on Sat, 2009/08/01 - 1:38am
nakul replied on Tue, 2009/10/20 - 1:49pm
in response to: thejavafreak
markgrant1st replied on Wed, 2009/11/04 - 2:27am
Wish I could learn the programming . I know each and everything about the Markets but computer, me and computer can not be friends at all :)\
Home Insurance Rates
Teddy P replied on Thu, 2009/11/05 - 9:46am
abcdentist replied on Sun, 2009/11/08 - 2:57am
hpmedia replied on Sun, 2009/11/08 - 8:44pm
hpmedia replied on Tue, 2009/11/17 - 5:40pm