So far, you’ve only altered the return value of a join point. That represents sig- nificant power. However, around advice can also change the context in which a join point executes. With proceed(), you can alter the arguments and target of a join point as well.
A d v i c e
As an example, let’s return to the employee management system. Let’s say that constant outages on the payroll server plague the NotifyPayrollAspect. Raises are being denied because the servers are occasionally down. To remedy this sit- uation, you’d like to retry the payroll notification with several of the company’s payroll servers before giving up.
Setting Up the Outages
The code you’ve written so far does not use an actual server—only a local object that couldn’t possibly be down. To simulate the fictional problems experienced by the system, you’ll use advice again. Check out Listing 7.22 for the code.
Around Advice 173
public aspect NetworkCalls {
pointcut callsToServer(PayrollServer server) : call(public * PayrollServer+.*(..)) && !call(new(..)) && !call(* get*()) && target(server);
}
public aspect OutageSimulator {
before() :
NetworkCalls.callsToServer(PayrollServer){
if(Math.random() > .5){
throw new UnavailableException(); }
} }
//output
LogRaises.java:7-Employee Omar:$50,000.00 to receive raise of 6000
FailedRaisePolicy.java:19-Raise of 6000 failed on Employee Omar:$56,000.00 because of raises.UnavailableException Example.java:25-After raise: Employee Omar:$56,000.00
Listing 7.22 An aspect simulates server unavailability.
The definition of the pointcut (calls to any public method except the construc- tor) appears in a different aspect because you’ll reuse it later. The advice in Out- ageSimulator throws an UnavailableException on 50 percent of these join points. You can see from the output that the simulated outage affected poor Omar—his representation as an Employee object now has a higher salary. Until the payroll server gets word of it, however, he won’t see a dime.
You need to notice two points before we move on to the main example. First, AspectJ excels at simulating error conditions such as these. Often, when you’re testing exception-handling code it’s difficult to provoke an actual error. AspectJ gets around this problem by letting you simulate failures at any join point. Sec- ond, the only calls to the PayrollServer in this code come from the NotifyPay- roll aspect. Aspects can advise each other. Anyone who has written advice that affects itself and ended up with a StackOverflowError will remember this, but it’s worth keeping in mind in case you’re tempted to think that aspects only affect component code.
Retrying the Call with Around Advice
To get Omar his raise, you need to retry the failed method call on several dif- ferent servers. You do this by using proceed() with different arguments. Look at the advice in Listing 7.23.
A d v i c e
174
public aspect RetryNotifications {
/**
* In reality the server choices might * come from polling the network. */
private PayrollServer[] servers = new PayrollServer[]
{
null,//placeholder for original target new PayrollServer("PayrollServer2"), new PayrollServer("PayrollServer3"), new PayrollServer("PayrollServer4"), };
void around(PayrollServer original) : NetworkCalls.callsToServer(original){
servers[0] = original;
int count = 0; while(true){
PayrollServer current = servers[count]; try{
System.out.println(
"Attempting to send update to " + current.getName()
Listing 7.23 RetryNotifications uses proceed() to try different servers in the event of failure.
Around Advice 175 ); proceed(current); return;//if successful } catch(UnavailableException e){ System.out.println("Network failure"); if(++count >= servers.length){ System.out.println("Alternates failed."); throw e; } //continue } }//while }//advice } //change to NetworkCalls
declare precedence : RetryNotifications, *;
//output
LogRaises.java:7-Employee Omar:$50,000.00 to receive raise of 6000
RetryNotifications.java:29-Attempting to send update to PayrollServer1
RetryNotifications.java:37-Network failure
RetryNotifications.java:29-Attempting to send update to PayrollServer2
PayrollServer.java:12-PayrollServer2
PayrollServer.java:13-Recalculating paycheck for Omar PayrollServer.java:15-New salary : 56000
Example.java:25-After raise: Employee Omar:$56,000.00
Listing 7.23 RetryNotifications uses proceed() to try different servers in the event of failure.
(continued)
There are several things to notice in the example. The around advice declares a return type of void, because the NetworkCalls.callsToServer pointcut only selects join points with void return values. More important, the call to pro- ceed() passes in a single argument of type PayrollServer. This requires some explanation.
Recall the pointcut that the advice uses:
pointcut callsToServer(PayrollServer server) : call(public * PayrollServer+.*(..)) && !call(new(..)) && !call(* get*()) && target(server);
The advice accepts the target of the join point as a formal parameter:
void around(PayrollServer original) :
NetworkCalls.callsToServer(original){...}
Because the advice takes the target as a parameter, AspectJ requires that the join point invoked by proceed() receive a target of the same type. When pro- ceed(current) executes, the join point call will happen with whatever target
currentreferences at the time. In other words, the advice will retry the method call on different servers until it succeeds (or runs out of options). In the output shown, the advice retries the call to recalculatePaycheck on Server2 when Server1 fails. Because the call to Server2 succeeds, the around advice returns, and execution continues normally. Omar gets his raise.
Implications of Changing Targets with Around
In this case, switching the targets of a method call had few implications for the behavior of the system. Relatively speaking, the system behaves just as it did before the advice was applied (except it tolerates a higher degree of server unavailability). Despite this fact, the implications for how you think about pro- gramming are profound. A single method call has been (potentially) converted into several method calls—each with a different target.
You can imagine even wilder alterations. Remember the malicious (but ineffec- tive) advice that redirected raises?
before(Employee emp): raises(emp, int){ emp = new Employee("me", 60000); }
It could work with around advice:
void around(Employee emp) : Raises.raises(emp, int){ proceed(new Employee("me", 50000));
}
//output
LogRaises.java:11-Employee me:$50,000.00 to receive raise of 5000
In practice, this advice yields somewhat nonsensical results, but it serves for illustration.