2.6 Implementing the Notification Policy
Section 2.5 showed you how to declare how you would like the world to be. This is the section were we get to make it that way. Remember that when we started on this journey, we had the simple requirement that whenever the state of a policy is updated, we should notify all of its listeners. It seems that a useful next step would be to write a pointcut that captures all the join points where the state of a policy is updated. We could call it policyStateUpdate:
pointcut policyStateUpdate() : execution(* set*(..)) && this(PolicyImpl);
This pointcut defines a policyStateUpdate to be the execution of any method whose name begins with "set," returning any value and taking any arguments. In addition, the object executing the method must be an instance of PolicyImpl. Because we have been following the JavaBeans naming convention in our domain model, this pointcut matches the set of state-updating methods in the policy class hierarchy very well. If we type this pointcut declaration into the editor buffer and save it, the editor should now look like Figure 2.28.
Figure 2.28 Adding the policyStateUpdate pointcut.
We have a way of matching all the join points where the state of a policy is updated. Now all we need to do is find a way to specify some action to take at those join points (notifying listeners). What we need is advice.
2.6.1 Introducing Advice
Pointcuts match join points, but advice is the means by which we specify what to do at those join points. AspectJ supports different kinds of advicebefore advice enables you to specify actions to take before a matched join point, after advice enables you to specify actions to take after a matched join point, and around advice gives you complete control over the execution of the join point. In our case we want to notify listeners after returning from a policyStateUpdate:
after() returning : policyStateUpdate() { // do something }
Figure 2.29 shows what happens when we type this into the editor buffer and save it. You can see that after and returning are AspectJ keywords. Also notice the similarities between the advice block and a methodboth can take parameters (although we have none here yet), and both specify a block of code to execute when they are called. A key difference though is that methods are called explicitly, whereas the advice is implicitly invoked by AspectJ whenever a join point matching its associated pointcut expression occurs. Chapter 7 contains a full discussion of advice in AspectJ.
Figure 2.29 Adding advice to the aspect.
2.6.2 Calling the Notify Method
Finally we get to implement the advice body and put in the call to notifyListeners. All we need to do is put in a call to policy.notifyListeners() in the body of the advice:
after() returning : policyStateUpdate() { policy.notifyListeners(); }
If we enter this into the editor buffer, and save, the compiler tells us that there is a small problem with our implementation as it stands (see Figure 2.30). "policy" cannot be resolved, the variable is not defined. How can the advice get ahold of the policy object whose state has just been updated?
Figure 2.30 "policy cannot be resolved."
We need to pass the policy object into the advice as a parameter, which is done in the same way as specifying parameters for methods:
after(PolicyImpl policy) returning : policyStateUpdate() { policy.notifyListeners(); }
Let's try that out in the editor. Figure 2.31 shows the result: a "formal unbound in pointcut" error.
Figure 2.31 Formal unbound in pointcut.
What could that mean? Recall that unlike a method, there are no explicit calls to advice. So if you do not call the advice explicitly, passing in the parameters it needs, from where does the advice get its parameter values? The answer is that the advice parameter values have to be provided by the pointcut: When the pointcut matches a join point, it needs to extract some information from that join point (in our case, the policy object that has just been updated), and pass it into the advice. The error message is telling us that the "formal" (advice parameter) is not "bound" in the pointcutor to put it another way, the pointcut is not giving the advice the parameter it needs yet.
Take another look at the definition of the policyStateUpdate pointcut:
pointcut policyStateUpdate() : execution(* set*(..)) && this(PolicyImpl);
This matches any join point that is the execution of a method whose name begins with "set," where the currently executing object (the object bound to "this" within the method body) is an instance of PolicyImpl. What we need is for the pointcut to tell us not just whether the currently executing object is an instance of PolicyImpl, but which instance it is. The set of values provided by a pointcut when it matches a join point is specified in its parameter list:
pointcut policyStateUpdate(PolicyImpl aPolicy) : ...
(So that's what those parentheses after the pointcut name are for!) Now it just remains to specify where the value of the policy parameter comes from. This is done via name binding in the pointcut expression:
pointcut policyStateUpdate(PolicyImpl aPolicy) : execution(* set*(..)) && this(aPolicy);
This revised pointcut expression matches the same join points as the previous version (the execution of any method whose name begins with "set," and where the object executing the method is an instance of PolicyImpl), but also makes available the actual PolicyImpl object at each join point it matches.
We are nearly there now; we just need a way to say that the policy object provided by the pointcut when it matches a join point should be matched to the policy parameter we specified in the advice definition. This is done by name binding, too:
after(PolicyImpl policy) returning : policyStateUpdate(policy) { policy.notifyListeners(); }
If we make these changes in the editor buffer, the aspect compiles successfully. Figure 2.32 shows the completed aspect in the editor.
Figure 2.32 The completed PolicyChangeNotification aspect.
The next section shows you how you can use the tools to understand the effect of the advice that we just wrote.