Annotations and Advice, By Example

Robert Rice Development Technologies Leave a Comment

Attention: The following article was published over 12 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

For me, its much easier to learn new concepts by way of concrete, real life examples, rather than by vague scenarios created for the sole purpose of working through a tutorial. What follows is a description of a real problem encountered in my work, and how I utilized annotations and point cut advice to implement a solution.

The system I am currently working on must often make web service calls to another “off-the-shelf” system. Both systems maintain their own databases of separate but complimentary information. Consequently, sometimes these web service calls result in persisted changes to the data on the companion system. Users also have direct access to this companion system through a web UI, and can edit and persist changes directly. As you can imagine, this potentially causes a problem in ensuring that only one “actor” is making changes to the data at a time. Each web service call is a separate and distinct action (rate, suspend, etc); one of these actions we can perform is a “lock” on the target data. Once the data is locked by an actor, no other actor can make changes to that data (neither through the other system, nor through the web UI) until it has been unlocked. Consequently, for every action performed in the parent system that changes data in the companion system, we must first make a web service call to lock the data, then a call to unlock the data once the updating action is complete.

Before annotations and point cuts, we might have implemented something like the following. First, create a class that implements the locking and unlocking actions to wrap around our method calls. Note that this requires that each method call be implemented in its own class.

Public class LockedTransaction {
	private String lockId;
	private Gateway gateway;

	public LockedTransaction(Lockable lockable){
		this.lockable = lockable;
	}

	public interface Lockable {
		public void transact();
	}

	public void executeLockedTransaction(){
		lockId = gateway.lock();
		try{
			lockable.transact();
		} finally {
			gateway.unlock(lockId);
		}
	}
}

Each class then making an applicable call to the companion system would implement the Lockable interface, defining its functionality in the transact() method.

Public class LockableImpl implements Lockable {
	public void transact(){
		//implementation goes here...
	}
}

And when this functionality needs to be executed, we instantiate and make the call this way.

LockedTransaction.Lockable = new LockableImpl();
New LockedTransaction(lockable).executeLockedTransaction();

There is a much cleaner and simpler way to do this with annotations and point cut advice. Point cuts can be defined to perform actions either before, after or around a method call. Since our scenario requires both a lock and unlock, and because the lock id returned by the lock action must be available to the unlock action, we will need to implement point cut advice  that performs actions around our method calls. But first, we should create a custom annotation to use on those methods we wish to perform a locking action on.

public @interface LockableAnnotation {
}

Then, we define the around advice that will be executed when an annotated method is called.

public aspect Lockable {
	private Gateway gateway = new Gateway();

	void around() : call(@LockableAnnotation public * *.*(..)){
		System.out.println("Locking");
		String lockId = gateway.lock();
		proceed();
		System.out.println("Unlocking");
		gateway.unlock(lockId);
	}
}

Finally, we create a class to test our implementation.

public class LockableTest {

	public static void main(String[] args) {
		LockableTest lockableTest = new LockableTest();
		lockableTest.performLockableAction();
	}

	@LockableAnnotation
	public void performLockableAction(){
		System.out.println("Performing Lockable Action");
	}
}

If you run this test, you should now see the output as follows…

Locking
Performing Lockable Action
Unlocking

…which shows that our around advice is performing the lock and unlock as expected.
There are several benefits to implementing this scenario using the annotations and advice. These include the following:

  1. The entire implementation is much more succinct and clear.
  2. Calling lockable methods does not require the programmer to wrap the method call in a lockable transaction. They simply need to call the method; the locking functionality it taken care of for them.
  3. It is no longer required to implement each method call as its own class.

— by Robert Rice, [email protected]

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments