Formal parameters pass join point context to advice in an explicit and type-safe fashion. Most of the information that can be passed in as advice parameters can also be extracted via reflection. Each type of access has advantages and disad- vantages. We’ll discuss which to use when in the section “Reflective Access to Join Point Context.”
Context Available as Formals
Chapter 6 discussed in detail how to pull context out of join points with point- cuts. To review, advice can use four types of context:
■■ Arguments—(Selected with args().) These can be actual method or con-
structor parameters. They can also be less formal types of arguments, specifically exceptions passed to handler blocks and field assignment val- ues.
■■ The currently executing object—(Selected with this().) Remember that
this context is not available during the execution of a static method.
■■ The target of a method/constructor call or field reference—
(Selected with target().)
A d v i c e
■■ The return value or thrown exception from a join point—(Selected with returning() or throwing().) This type of context can only be accessed by after advice of the correct type.
The Left-Right Rule
It’s important to remember that every named parameter on the left side of the colon must match a piece of context exposed by the pointcut on the right side of the colon. Consider the raise logging advice discussed earlier:
before(Employee emp, int amount) : raises(emp, amount) { System.out.println(emp + " to receive raise of " + amount);
}
Here Employee emp on the left side matches raises(emp, amount) on the right side. The same does not necessarily apply in the other direction—the right side can expose more context than is used by the left side. The Left-Right rule applies to pointcuts as well as advice. For example, in the definition of the raises pointcut, Employee emp on the left matches target(emp) on the right. Unlike method parameters, the order in which the parameters appear doesn’t matter; AspectJ will match up the identifiers for you. You could swap the order of the parameters without affecting the program:
before(int amount, Employee emp) : raises(emp, amount) { System.out.println(emp + " to receive raise of " + amount);
}
Parameter-Context Mismatches
Sometimes you can construct a pointcut that seems to match all the required parameters but actually doesn’t. Let’s say you wanted to add cost of living increases to the raises pointcut:
pointcut raises(Employee emp, int amount) : call(void raiseSalary(int)) &&
target(emp) && args(amount) //amount bound ||
call(void costOfLivingAdjustment()) && target(emp);//amount not bound
In this pointcut, args(amount) appears on one side of the || but not on the other. The compiler will complain. Chapter 6 has more examples of this behavior.
Formal Parameters Are Like Method Parameters
If you’re experienced with the Java language, you should find it natural to work with advice parameters. After you enter the body of the advice, the advice
parameters behave just like method parameters. The advice body can call meth- ods on the parameters, pass them to other methods, or store them in a data structure. Like method parameters, advice parameters are local to the advice. In other words, if you reassign a parameter, it will have no effect outside the advice. However, you can change the state of the parameters and have the change reflected outside the advice.
Reassignments.To illustrate this, consider some malicious aspect code. Here
an unscrupulous programmer attempts to reassign the target of a raise:
before(Employee emp): raises(emp, int){ emp = new Employee("me", 60000); }
First (as discussed in the previous section), note that the advice doesn’t need to use the amount context exposed by the raises pointcut. This snippet illustrates the preferred method of indicating that you don’t wish to bind a piece of con- text: replace the identifier with the type of the context (replace the identifier “amount” with type “int” in the pointcut).
Recompiling with this piece of advice yields output like the following:
LogRaises.java:7-Employee Rebekah:$50,000.00 to receive raise of 5000
Example.java:16-After raise: Employee Rebekah:$55,000.00 ...
In other words, the advice does not affect the actual raises. The effect of “emp =” lasts only until the end of the advice body. It’s possible to do what this malicious advice attempts (change the target of a method call), but only with
around advice—and the syntax is more complicated.
State changes. It’s much easier to affect join point context by changing the
state of formal parameters. Consider the following advice:
before(Employee emp): raises(emp, int){ emp.costOfLivingAdjustment(); }
This advice adds a cost of living adjustment to the employee’s base pay before any raise. You can see from the output that this advice does affect the employ- ees in Example:
LogRaises.java:7-Employee Rebekah:$50,125.00 to receive raise of 5000
Example.java:16-After raise: Employee Rebekah:$55,125.00 LogRaises.java:7-Employee Omar:$50,125.00 to receive raise of 6000
Example.java:16-After raise: Employee Omar:$56,125.00 ...
A d v i c e
If you inspect the output closely, you can see that the advice we just looked at takes effect before the advice that logs the raise attempt. That’s interesting: Does the cost of living adjustment count as part of the raise? If so, shouldn’t it be applied after the log? AspectJ allows the programmer to control the order of advice execution through advice precedence, which we’ll discuss toward the end of the chapter. For the moment, remove both of these pieces of advice.
Object-typed Parameters and Boxing
Suppose you want to write a pointcut that encompasses widely varying join points. For instance, say you’re debugging Employee and you want to track changes in state. The aspect in Listing 7.5 uses the set() pointcut to log any assignments to a field of Employee.
Issues Common to All Types of Advice 143
public aspect TrackSets {
before(Object newValue) :
employeeFieldSets(newValue){
String name =
thisJoinPoint.getSignature().getName(); System.out.println("The new value of " +
name + " is " + newValue); }
pointcut employeeFieldSets(Object newValue): set(* *) && target(Employee) &&
args(newValue); }
//output
TrackSets.java:14-The new value of formatter is java.text.DecimalFormat@67500
TrackSets.java:14-The new value of name is Rebekah TrackSets.java:14-The new value of salary is 50000
LogRaises.java:7-Employee Rebekah:$50,000.00 to receive raise of 5000
TrackSets.java:14-The new value of salary is 55000 Example.java:16-After raise: Employee Rebekah:$55,000.00 ...
Listing 7.5 Object boxing in action.
The pointcut employeeFieldSets uses set(* *) to target all field sets, and then narrows the selection to those join points where the target is an Employee (target(Employee)). It exposes the argument of the field assignment
with args(newValue). The advice accepts the args context through its one for- mal parameter: Object newValue.
The output shows that the pointcut works as described. Formatter is set to a DecimalFormat, name is set to the String “Rebekah”, and salary is set to 50000. But Salary is an int field—how did it enter the advice through a formal parame- ter with the type of Object? If you read Chapter 6, you might remember that AspectJ automatically boxes primitives to their wrapper types. In other words, when it entered the advice, the “50000” you saw in the output was an object of type java.lang.Integer. This boxing behavior is convenient for situations when context must reflect both object and primitive types.