« AspectJ and AJDT going strong... | Main | What's on your bookshelf? »
January 18, 2006
Typed Advice in Spring 2.0 (M2)
I spent a few days last week working on argument binding in advice for the new aop schema and @AspectJ support in Spring 2.0. In this article, I'll explain how the new support works and how you can use it.
First some background for those of you who haven't been following along with developments in Spring 2.0. Spring 2.0 supports schema-based configuration as well as DTD based. Schema gives better validation, a better experience in the IDE (in terms of code completion etc.), and a more concise and readable configuration file. It's important to state that however you specify your bean definitions (DTD-based, schema-based, script-based,...) everything comes down to the same bean metadata model at runtime so the different styles are compatible and interchangeable. For AOP support, we have the schema http://www.springframework.org/schema/aop/spring-aop.xsd.
This is what a skeletal config file looks like using the new schema support:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
The various AOP-related definitions are placed inside an <aop:config> element. Here's a cut-down example of a simple aspect from the test suite:
<aop:config> <aop:aspect id="beforeAdviceBindingTests" ref="testAspect"> <aop:advice kind="before" method="oneIntArg" pointcut="execution(* setAge(..)) and args(age)" /> </aop:aspect> </aop:config> <bean id="testAspect" class="org.springframework.aop.aspectj.AdviceBindingTestAspect"/>
I'm declaring an aspect called "beforeAdviceBindingTests", the aspect instance (holding the state for the aspect) is the bean "testAspect". This is just a regular bean, that can be configured by Spring in all the usual ways. The implementing class (in this case, AdviceBindingTestAspect") is just a regular POJO. The aspect contains only one piece of advice, which is "before" advice (you can specify one of the five AspectJ advice kinds in the "kind" attribute: before, afterReturning, afterThrowing, after, or around). The advice body is implemented by the method "oneIntArg", and the associated pointcut expression matches the execution of any method named "setAge" taking a single int argument. The argument is bound to a parameter named "age".
What this all boils down to, is that any time a matching method executes on a Spring bean in the application context, the "oneIntArg" method will be called on the "testAspect" bean, passing the age as a parameter.
Note that we've gained two important things here: the aspect class and advice method do not need to implement any special interfaces, and we have typed advice. By typed advice, I mean that the advice signature declares the parameters it needs, and gets passed those parameters only, preserving type information. Contrast this to typical AOP framework approach of always passing an Object[] from which the advice body must pick-and-choose and cast as needed.
To complete this introductory example, here's the aspect class:
public class AdviceBindingTestAspect { public void oneIntArg(int age) { // whatever we like here, using "age" as needed } }
The combination of the class declaration and XML definitions has the same effect as the following AspectJ aspect declaration:
public aspect AdviceBindingTestAspect { before(int age) : execution(* setAge(..)) && args(age) { // whatever we like here, using "age" as needed } }
So let's turn our attention to the main subject of this article: how does the binding support for typed advice work and how can you use it?
Determining argument names and types
Look at the AspectJ aspect definition I gave you above for a moment. The advice declares a parameter, "age" of type int, and in the pointcut expression, "age" is bound by the "args" clause. The expression "args(age)" actually does two things: it restricts matching to join points where there is a single argument of type "int" (the type of "age"), and it binds the argument value to the "age" parameter.
In the Spring 2.0 version, we have exactly the same pointcut expression, but the method declaring the "age" parameter is defined elsewhere - in the AdviceBindingTestAspect class. Java reflection lets us down at this point. All we can determine is that the "oneIntArg" method takes a single parameter of type "int": we can't determine the parameter names. In this case since the advice method takes only one argument, and the pointcut binds only one argument, you could deduce that the int parameter must corresponding to "age", but when binding multiple arguments this may not always be possible. Matching argument names to argument types is critical to the matching process, and of course to passing arguments to the advice method itself.
Spring 2.0 resolves this issue by supporting a number of "ParameterNameDiscoverers". ParameterNameDiscoverer is a strategy interface, and multiple implementations are arranged in a ChainOfResponsibility. Spring attempts to discover argument names by applying the following strategies in turn:
- If the argument names are explicitly specified, then the
given argument names are used. Argument names can be specified in
one of two ways:
- They can be specified using the "arg-names" attribute of the <:aop:advice> element. In the example we've been working with so far, we could have specified arg-names="age" for example. The string value of this attribute takes a comma-delimited list of argument names.
- If using the @AspectJ notation to define aspects, then the advice annotations (@Before, @AfterReturning, @AfterThrowing etc.) all support an optional "argNames" attribute. Again, this takes a comma-delimited list of argument names.
- If argument names have not been explicitly specified, then we attempt to recover the argument names from the class file of the class defining the method. This is possible so long as the class was compiled with debug information (at a minimum, -g:vars). From the user perspective this is certainly the easiest and most natural way of obtaining argument names. Compiling with debug information makes it much easier to diagnose problems in running code (stack traces can include source file and line number information for example). If you are worried about overhead, then -g:vars will ensure that the class files contain the minimum amount of debug information (LocalVariableTables) that Spring needs for this mechanism to work. What are the downsides of -g:vars? Your class files will be very slightly larger (this will not be an issue in the vast majority of cases), certain optimisations that the compiler would otherwise make (pertaining to the removal of unused local variables for example) will not be made, and reverse engineering of your application is slightly easier. If you are worried about the sound of lost optimisations, run some performance tests - you may well find the effect is not noticeable.
- If local variable table information is not available for the method, then Spring tries to deduce the argument names from the pointcut expression and the signature of the method. Consider a method that takes 2 parameters, one of a reference type, and one of a primitive type. Given a pointcut expression that binds one variable using "this()" and one variable using "args()" we know that the primitive argument must be the one bound by args, because "this" must be a reference type. In the AspectJ pointcut language all of the binding pointcut designators have two forms: one that takes a type name, and one that takes a variable name. For example, I can write "this(Float)" which simply matches any join point where "this" is an instance of Float, or I can write "this(f)" which matches any join point where "this" is an instance of the type of "f", and binds the value to the "f" argument. To find out what the variables in the pointcut expression are, Spring uses the simple assumption that any string inside a binding pointcut expression that is a valid Java identifier name and starts with a lower case letter is a variable, and anything else is a type.
Using typed advice
Let's look at some of the features available.
Information about the current join point
Firstly, if any advice method has a first argument of type org.aspectj.lang.JoinPoint then a JoinPoint instance representing the current join point will be passed to the advice method. This provides equivalent functionality to the use of 'thisJoinPoint' within AspectJ advice. The JoinPoint object can be useful when writing generic advice that applies across a wide range of join points since it can give you reflective information about the join point, such as the method being executed under the join point. For around advice, you must use the type org.aspectj.lang.ProceedingJoinPoint instead of JoinPoint - this subtype of JoinPoint provides the critical "proceed" method that you must call when you wish to proceed with the computation under the join point.
As an alternative to JoinPoint, a first parameter of type org.aspectj.lang.JoinPoint.StaticPart will be passed an instance of JoinPoint.StaticPart instead of JoinPoint. This provides only statically available information about the join point (method and signature for example) but not the arguments and target object for the invocation being advised. Use of JoinPoint.StaticPart instead of JoinPoint may allow optimisations inside the AOP framework in the future. For now it is no more efficient than JoinPoint when using Spring AOP.
Here's a simple example using JoinPoint from the test suite;
XML fragment for advice definition
<aop:advice kind="afterReturning" method="needsJoinPoint" pointcut="execution(* getAge())" />
Advice method declaration:
public void needsJoinPoint(JoinPoint tjp) { this.collaborator.needsJoinPoint(tjp.getSignature().getName()); }
And the test case that verifies a suitable JoinPoint object is passed to the advice method:
public void testNeedsJoinPoint() { mockCollaborator.needsJoinPoint("getAge"); mockControl.replay(); testBean.getAge(); mockControl.verify(); }
(I'll write more about the approach to testing aspects I've taken here in a separate post....)
Method execution arguments
You've already seen an example of using "args" to bind the arguments at the method execution join point (remember than Spring AOP only supports method execution join points). You can read up on "args" (and all of the other AspectJ pointcut designators" in the AspectJ programming guide at the AspectJ website. "args" restricts matching only to the execution join points for methods that have a corresponding number of arguments, and can bind one or more of those arguments.
Here are some very simple examples:
- args()
- matches the execution of methods that take no arguments (so clearly there is nothing to bind here
- args(..)
- matches the execution of methods that take zero or more arguments (but still performs no binding)
- args(x,..)
- matches methods with one or more parameters, that have a first parameter of the type of "x", and bind the argument value to "x"
- args(x,*,*,s,..)
- a more contrived example. Matches the execution of methods that take at least 4 parameters. The first parameter must be of the type of "x", and the argument value will be bound to "x". The second and third parameters may be anything, but the fourth parameter must be of the type of "s" and the argument value will be bound to "s".
By combining named arguments, "*" (which matches a single argument of any type), and ".." (which matches zero or more arguments of any type) you can expose exactly the context information you need to the advice that executes at the join point.
Method return values
If your advice needs access to the return value of a method execution join point you can use afterReturning advice and bind the returned value using the "returning" attribute.
Here's a simple example:
<aop:advice kind="afterReturning" method="afterGettingName" returning="name" pointcut="execution(* getName())" />
Couple this with the method definition
public void afterGettingName(String name) { // advice body }
and "afterGettingName" will be invoked on every successful return from an invocation of "getName" with the return value passed in as the parameter 'name'.
Note that the "returning" clause is doing two things here: it is restricting matching to only those method execution join points that return an instanceof String (the type of 'name'), and it is binding the return value to the parameter name.
If you use the returning attribute with a type name (rather than a variable name) it restricts matching to only those join points where the return value is an instance of the given type (but does not pass the actual return value to the advice).
Thrown exceptions
afterThrowing advice runs when an advised join point exits by throwing an exception. Often it's useful to have access to the actual exception that was thrown in the advice. You can use the "throwing" attribute to do this.
Consider the following aspect definition:
<aop:aspect id="legacyDaoExceptionTranslator" ref="exceptionTranslator"> <aop:pointcut name="legacyDaoMethodExecution" expression="execution(* org.xzy.dao..*.*(..))"/> <aop:advice kind="afterThrowing" method="translateException" pointcut-ref="legacyDaoMethodExecution" throwing="hibEx" /> </aop:aspect>
and the accompanying method definition
public void translateException(HibernateException hibEx) { throw SessionFactoryUtils.convertHibernateAccessException(hibEx); }
The advice will run whenever a legacy DAO method (written without Spring ;) ) throws a HibernateException, passing the thrown exception to the advice method where it can be converted to a Spring DataAccessException and rethrown.
(yes, that is all the code you need :) )
The executing instance
Consider the execution of a method that would result from a call to "testBean.getAge()". What if my advice needs access to the testBean instance on which the method executes? The pointcut designators "this" and "target" provide access to this instance. In AspectJ, both this and target are bound to the executing instance at the join point. In Spring AOP an advised object will be proxied. Using the "this" pointcut designator will bind the proxy object (the value of "this" for the object instance that represents the bean), and using the "target" pointcut designator will bind the true target.
For example:
<aop:advice kind="afterReturning" method="setAgeOnATestBean" pointcut="execution(* setAge(..)) and args(age) and this(bean)" arg-names="age,bean" />
This advice passes with the following test case:
public void testOneIntAndOneObjectArgs() { mockCollaborator.setAgeOnATestBean(5,testBean); mockControl.replay(); testBean.setAge(5); mockControl.verify(); }
If we use "target" instead of "this": "execution(* setAge(..)) and args(age) and target(bean)" the test would fail. The mock collaborator would be expecting "testBean" (which will be an AOP proxy) to be passed as an argument, but because we used "target", the proxy target object will be passed instead. This is demonstrated by the following test case:
public void testTargetBinding() { Advised advisedObject = (Advised) testBean; TestBean target = (TestBean) advisedObject.getTargetSource().getTarget(); mockCollaborator.setAgeOnATestBeanTarget(5,target); mockControl.replay(); testBean.setAge(5); mockControl.verify(); }
Annotations
The AspectJ pointcut language provides rich support for matching and binding annotations. For Spring applications, the two most useful pointcut designators are probably "@annotation" and "@within".
"@annotation" matches when the subject of the join point has an annotation of the given type. For Spring, this means when the method being advised has an annotation of the given type. For example, the advice:
<aop:advice kind="before" method="beforeTxMethodExecution" pointcut="@annotation(tx)" />
when coupled with the signature of the "beforeTxMethodExecution" method (in order to determine the type of annotation that "tx" will match):
public void beforeTxMethodExecution(Transactional tx) { if (tx.readOnly()) { ... }
will match the execution of any bean method that has the "@Transactional" annotation. Remember that Spring is implicitly limited to execution join points only. The corresponding pointcut when using the full AspectJ language would be: "execution(* *(..)) && @annotation(tx)".
"@within" matches any join point within a type that has a given annotation. So to match execution of any bean methods within a type that has the "@Transactional" annotation we could write:
<aop:advice kind="before" method="beforeTxMethodExecution" pointcut="@within(tx)" />
The @AspectJ style
All of the examples I've shown you so far have used the XML schema form of aspect definition. Spring 2.0 can also make use of aspects written using the "@AspectJ style". It's out of scope for this blog entry to give a full tutorial on @AspectJ, but here's a quick example of how it works...
The first thing that you need to do is enable auto-proxying from @AspectJ aspects in your application context. This is trivial using the new schema support for AOP:
<aop:aspectj-autoproxy/>
Then if we define any beans in the application context that are actually @AspectJ aspects (annotated using the AspectJ annotations), Spring will automatically use those beans to configure AOP proxies. The transactional method execution example, written as an @AspectJ aspect, would look like this:
@Aspect AnnotationDrivenTransactionManager { @Before("execution(* *(..)) && @annotation(tx)") public void beforeTxMethodExecution(Transactional tx) { // ... } }
To use this aspect, we would simply add a definition such as the following to the configuration.
<bean class="...AnnotationDrivenTransactionManager" >
of course, all of the usual dependency injection capabilities are avaible for configuring the aspect.
One nice advantage of using this style of aspect definition, is that you can switch from using Spring AOP proxying to full AspectJ weaving simply by removing the "aspectj-autoproxy" element from your configuration, and compiling (or binary weaving) your application using AspectJ instead. If you are dependency injecting those aspects, then either their bean definitions have to be changed slighty to obtain the aspect instance for injection via an AtAspectJFactoryBean, or you could simply annotate the aspects as @Configurable and use the spring-aspects.jar aspect library.
Posted by adrian at January 18, 2006 10:05 AM [permalink]
Comments
Good article. Where can I find some more info on Spring 2? I have been searching but have not found anything on what the new features are or how to use them. I found some problems and don't know if they are limitations, bugs or just not ready yet for S2. For example, I can only use "execution" as "call" throws an exception as illegal. Also, when I try to have a method with multiple signatures I get an IllegalArgumentException - wrong number of arguments.
Posted by: mantis at February 1, 2006 01:48 PM
The reference guide that ships with Spring is in the process of being updated, but docs are currently lagging implementation at the moment. Spring AOP is limited to execution join points (the framework doesn't deal with call jps for example), so the only "kinded" pcd you can use in pointcuts for Spring AOP is "execution". You have to switch to full AspectJ to get support for the other join point kinds.
The method name lookup code used to return just the first matching method by name. Rob was planning to enhance this to support overloaded methods, but I'm not sure if that work has been done yet. Please raise a JIRA issue on this if your program is still not working correctly under Spring 2.0 M2.
Posted by: Adrian Colyer at February 7, 2006 10:43 AM
Thank you very much for the article. Ever since I started toying with Spring and AOP I felt that something extremely important was happening. However, AspectJ was a bit "special" with regard to the new syntax and Spring lacked a sophisticated AOP mechanism. I am very glad to see, that there is now a consolidation and that the two technologies are merging together.
There are some issues though, which irretated me, while exploring the possibilities of Spring and AspectJ. Most of them hint, that AspectJ is currently not implemented to be fully orthogoal to Spring's mechanisms. Here are two things, that puzzled me recently:
1) When intercepting (@After) e.g. all setters of my business classes the property initialization performed by Spring does not seem to trigger the advice. So I assume it works by accessing the setter methods directly, and not through the proxies, right? Would there be any way to stimulate this behaviour?
2) When using the @DeclareParent advice, the "defaultImpl" class does not seem to be treated like other beans, i.e. if I try to apply an advice (e.g. @After) to the introduced interface's default implementation methods, these are not recognized. If I create an instance of these beans manually (AppCtx.getBean()) this however works. So to me it appears, that the proxy does not manage the introduced methods in the same way, as those of the unmodified implementation class. Will this change in the future, or are there some tricky reasons for this behaviour?
Posted by: Oliver Albrecht at February 14, 2006 12:28 PM
The first of these issues is related to the fact that it is still the same Spring-AOP Proxy-based implementation being used to do the weaving. Spring treats the assembly and configuration of the target object as something that happens before a proxy to the target is created. It caught me out too first time round. Currently there is no way to get around this. It may be that we should consider changing Spring's behaviour in this regard - but we would have to think carefully about backwards compatibility if we made such a change.
On the second issue, you're right that Spring treats the defaultImpl class as providing a default implementation for the interface methods, but does not proxy the default implementation itself. To adhere more closely to AspectJ semantics, it probably *should* honour advice on the introduced operations. This is tricky in the implementation and again something that also needs to be looked at from a backwards compatibility viewpoint
Please do raise JIRA issues on these points if you'd like to see a change here. Thanks, Adrian.
Posted by: Adrian Colyer at February 20, 2006 02:10 PM
Thank you for this excellent article! I have a question concerning accessing the active joinpoint object within the advice. Here is a little example: I have an advice class "Authentication" with a method "authenticate(Account a)". The pointcut comprises all public methods of the "Account" class. I created a schema based aop configuration and created beans for the "Authentication" and the "Account" class (id="account"). The pointcut expression looks like this "execution(* banking.Account.*(..)) and this(account)" . I bind the account object to the advice with arg-names="account". The Problem is that account is not found and if I change "account" to "banking.Account" in the pc expression I cannot pass the parameter to the advice. Anyone an idea? Thx!
Posted by: hatsch1209 at July 9, 2007 09:45 AM
Nice article and thanks for this detailed explaination.
Posted by: popular at April 19, 2009 02:58 PM
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)