• No results found

Stopping Unwanted Join Points

In addition to paving the way for join points by setting up state, before advice can also stop join points from executing if conditions aren’t right. It does so by throwing an exception. This capability allows for flexible pre-condition checks.

To illustrate this capability, let’s return to the personnel management applica- tion. For reasons of their own, management has decided only to allow raises of a minimum amount. Translating this requirement into technical terms, you decide to stop the execution of the raiseSalary method if the amount doesn’t meet the minimum ($6000). Listing 7.9 shows the example code that exercises the new capabilities.

Before Advice 151

public class Example {

public static void main(String[] args){ raiseAndCheck("Rebekah", 5000); raiseAndCheck("Omar", 6000); raiseAndCheck("Philipe", 7000); }

private static void raiseAndCheck(String name, int raise){ Employee e = new Employee(name, 50000); try {

e.raiseSalary(raise); } catch (Exception ex) {

System.out.println("Raise failed with " + ex); }

System.out.println("After raise: " + e); }

}

//output--indicates all raises are applied

LogRaises.java:7-Employee Rebekah:$50,000.00 to receive raise of 5000

Example.java:16-After raise: Employee Rebekah:$55,000.00 LogRaises.java:7-Employee Omar:$50,000.00 to receive raise of 6000

Example.java:16-After raise: Employee Omar:$56,000.00

LogRaises.java:7-Employee Philipe:$50,000.00 to receive raise of 7000

Example.java:16-After raise: Employee Philipe:$57,000.00

Listing 7.9 The example will signal any exceptions to the console.

Astute readers may be asking themselves at this point why the example appli- cation takes care of checking the Employee’s state and handling the resultant exception. Couldn’t you do it with advice? The answer is that you can—and you will, in the section about after advice. (Feel free to skip ahead if you’re inter- ested.)

To implement the policy, you need to get access to the amount of a raise. Perhaps you could reuse the pointcut defined when logging the raises.

To accomplish this end, move the raises pointcut into its own aspect. You can see the result in Listing 7.10.

A d v i c e

152

public aspect Raises {

public pointcut raises(Employee emp, int amount) : call(void raiseSalary(int)) && target(emp) && args(amount);

}

Listing 7.10 The Raises aspect now defines the raises pointcut.

Defining the raises pointcut in a separate aspect allows any aspect to reference (and thus reuse) the pointcut. Because you’ve already written two aspects that need it, this seems like a sensible choice. (Note that the pointcut has not changed since we first saw it.)

The aspect that enforces the minimum raise policy appears in Listing 7.11.

public aspect MinimumRaisePolicy {

private static final int MIN_INCREMENT = 6000;

before(int increment) :

Raises.raises(Employee, increment){

if(increment < MIN_INCREMENT){

throw new IllegalArgumentException("Raise of " + increment + " is less than department minimum."); }

} }

//output from Example's main method

Example.java:19-Raise failed with java.lang.IllegalArgumentException: Raise of 5000 is less than

department minimum.

Example.java:21-After raise: Employee Rebekah:$50,000.00

LogRaises.java:7-Employee Omar:$50,000.00 to receive raise of 6000

Example.java:21-After raise: Employee Omar:$56,000.00

LogRaises.java:7-Employee Philipe:$50,000.00 to receive raise of 7000

Example.java:21-After raise: Employee Philipe:$57,000.00

At each raise join point, the advice in MinimumRaisePolicy executes. The advice inspects the amount of the raise (passed into the advice as a formal parameter) and throws an exception if the amount is too low. The output indi- cates that the advice operates correctly. On the first line, a printout from the exception handler block indicates that the $5000 raise caused an exception. The next line indicates that Omar’s raise met the criteria and proceeded suc- cessfully.

Logging Failure: An Unintended Side Effect

If you look more closely at the output, you’ll notice a side effect of the new aspect: Rebekah’s raise was not logged. MinimumRaisePolicy’s advice sup- pressed the advice from LogRaises: The new advice executed before the advice written earlier. When MinimumRaisePolicy threw an exception, it prevented LogRaises’ advice from executing just as effectively as it prevented the actual raise. This sort of situation can arise whenever two pieces of advice apply to the same join point. Fortunately, AspectJ allows you to specify which advice takes precedence. The final section of the chapter, “Advice Precedence,” covers this topic in detail.

For the moment, you can solve this problem by adding the following line to the Raises aspect:

declare precedence : LogRaises, *;

This line uses a declare form (more on that topic in Chapter 8) to specify that LogRaises’ advice takes precedence over advice defined in any other aspect (hence the *). Now the output correctly logs the failed raise:

LogRaises.java:7-Employee Rebekah:$50,000.00 to receive raise of 5000

Example.java:19-Raise failed with

java.lang.IllegalArgumentException: Raise of 5000 is less than department minimum.

Example.java:21-After raise: Employee Rebekah:$50,000.00

Is an Exception Appropriate?

We set ourselves up to succeed with this example by inserting a catch block into the example code. If we had not, the IllegalArgumentException would have ter- minated the main method of the Example class. Because IllegalArgumentExcep- tion is an unchecked exception, there’s no automatic way for a caller to know that it should be caught. (Keep in mind that you could not convert it to a checked exception without modifying Employee—see the section on advice and excep- tions.) These issues shouldn’t scare you away from using exceptions to halt join points; however, throwing one may not be the right action to take. In these cases, around advice can implement a more flexible response.

Increased Modularity with Advice

Despite the issues we’ve raised, adding this business rule as an aspect rather than putting it directly into the component code has led to an increase in mod- ularity. The Employee class does not involve itself in the distinction between legal and illegal raises. You’ve separated the concern of “raise legality” from the concern of “applying the raise.” By separating the concerns, you can alter or replace each of them in isolation. Employee could be deployed in a different department, incorporated into a system with no departmental policy, or used in a situation with completely different rules about what defines an allowable raise. For instance, a different departmental policy aspect could request that a Manager object pre-authorize the change in salary.