Evaluating AOP Frameworks
If you are already using an AOP framework and would like to enhance your aspects using annotations, then you will most likely choose to stay with your current framework. However, if you are not yet using an AOP framework, then support for pre-Java 5 metadata should be a part of your evaluation criteria. A full evaluation of the many existing AOP frameworks is beyond the scope of this article; for a good comparison of AOP frameworks, consult the Related Resources in the left column of this article. My evaluation here focuses specifically on support for javadoc metadata in the most popular AOP frameworks.
When it comes to evaluating support for javadoc metadata, several factors merit consideration:
- Type Safe AnnotationsType safe annotations are typically defined as an interface or JavaBean and expose allowable attributes as properties and constructor arguments.
- Untyped AnnotationsSome frameworks allow you to access javadoc metadata without having to pre-define your annotations.
- Named ParametersAn annotation configuration that uses name-value pairs.
- Anonymous ParametersAn annotation configuration that uses single values or a comma-separated list.
- Target RestrictionThe process of restricting annotations to elements of a particular type such as classes, methods, or fields.
- Annotation InheritanceA term meaning that annotations defined on superclasses are available on subclasses.
- Annotation StorageA scheme for storing metadata so that it can be made available at runtime.
- Ant TaskDetermine whether the framework provides an Ant task to process javadoc metadata.
- Maven PluginDetermine whether the framework provides a Maven plugin to process Java javadoc metadata.
Table 1 shows a feature comparison of four popular AOP frameworks (AspectJ, AspectWerkz, Spring AOP, and JBoss AOP) support for javadoc metadata.
Table 1. Metadata Framework Comparison: Because metadata support prior to Java 5 is not standardized, each framework supports it differently.
| Feature
|
AspectJ
|
AspectWerkz
|
Spring AOP
|
JBoss AOP
|
| Typed Annotations |
JSR 175 Only (*) |
Yes |
Yes |
Yes |
| Untyped Annotations |
No |
Yes |
No |
Unknown |
| Named Parameters |
JSR 175 Only (*) |
Yes |
Yes |
Yes |
| Anonymous Parameters |
JSR 175 Only (*) |
Yes |
Yes |
Unknown |
| Target Restriction |
JSR 175 Only (*) |
No |
Yes |
No |
| Annotation Inheritance |
JSR 175 Only (*) |
No |
Yes |
Unknown |
| Annotation Storage |
Native |
Enhanced Classes |
Generated Classes |
Enhanced Classes or XML |
| Ant Task |
Yes |
Yes |
Yes |
Yes |
| Maven Plugin |
Yes (Maven 1) |
Yes (Maven 1) |
Yes (Maven 1) |
No |
*
JSR 175 bytecode compatible annotations can be created using Backport 175. However you will need to use load-time weaving of aspects to prevent the Backport 175 enhanced classes from being overwritten.
Example ApplicationTicket Broker
 | |
| Figure 1. Ticket Broker Sequence Diagram: Both the seat reservation and billing must be completed as an atomic unit of work. |
As an example I'll walk you through the process of building a ticket broker application, which allows customers to purchase tickets to events. As
Figure 1 illustrates, each order involves two steps: reserving the specified number of seats, and debiting the customer's account for the purchase. These steps should be executed within the context of a transaction. In other words, they should either both succeed or neither should happen at all.
To follow along,
download the companion source code for this article, which includes two versions of the Ticket Broker application. The first, named "initial," contains a simpler initial version of the application in which transactions are not properly applieda good version with which to start. The second, "final" version contains the completed application. See the
readme.txt file included in the download for instructions on building and running the example application.
| Author's Note: The example application uses the Ant Tasks for Maven 2 to manage dependencies. Maven 2 is a powerful tool to encapsulate oft-repeated build tasks. But this new version is still in beta and doesn't yet have robust plugin support. Using the Ant Tasks allows me to take advantage of Maven 2 dependency management while still being freed to use Ant with the AspectWerkz compilers. For more information on Maven 2 see my previous article. |
Here's the code for the
purchaseTicket operation.
...
public void purchaseTicket(int customerAccountID,
int eventID, int quantity)
{
reserveSeats(eventID, quantity);
chargeCustomerAccount(customerAccountID, eventID, quantity);
}
private void reserveSeats(int eventID, int quantity)
{
Event event = eventDAO.findById(eventID);
if (quantity > event.getAvailableSeats())
{
throw new RuntimeException("Not enough seats available");
}
event.setAvailableSeats(event.getAvailableSeats() - quantity);
eventDAO.save(event);
}
private void chargeCustomerAccount(int customerAccountID,
int eventID, int quantity)
{
CustomerAccount account = customerAccountDAO.findById(
customerAccountID);
Event event = eventDAO.findById(eventID);
double amount = event.getTicketPrice() * quantity;
if (amount > account.getBalance())
{
throw new RuntimeException("Not enough money in account");
}
account.setBalance(account.getBalance() - amount);
customerAccountDAO.save(account);
}
...
As you can see, this operation is composed of two steps. First, it attempts to reserve the seats requested by the customer. If the reservation succeeds, the operation then proceeds to charge the customer's account by calling the
chargeCustomerAccount method. If there are not enough seats, or if a customer's account contains insufficient funds, the
purchaseTicket method throws an exception. But if the
chargeCustomerAccount method throws an exception then the system needs to roll back the seat reservation made in the first step.
A unit test verifies that the transaction semantics have not yet been properly applied on the
purchaseTicket operation. This test provides confirmation that if a customer attempts to purchase more tickets than he can afford, the seat reservation is rolled back and his account balance remains unmodified. I have chosen to use DbUnit to populate the database with test data. The test data is shown below:
src/test/resources/test.db.xml
<dataset>
<event id="1" name="Willie Nelson Fourth of July Picnic"
price="75" seats="5000" />
<event id="2" name="B.B. King Sings the Blues" price="60"
seats="4" />
<event id="3" name="Norah Jones" price="100" seats="250" />
<customer_account id="1" number="111" balance="50000" />
<customer_account id="2" number="222" balance="1000" />
<customer_account id="3" number="333" balance="25" />
</dataset>
The test data contains three events and three customers. In the following scenario, the test case attempts to reserve two tickets for customer three to event threethe Norah Jones event. But as tickets to that event cost $100.00, and the customer account contains only $25.00, the customer cannot afford the tickets; in other words, the entire operation should be rolled back. Here's the test code.
...
public void testPurchaseTicketInsufficientFunds()
throws SecurityException, NoSuchMethodException
{
try
{
// Parameters are (customerId, eventId, quantity)
service.purchaseTicket(3, 3, 2);
fail("Exception not thrown when customer has " +
"insufficient funds");
}
catch (Throwable e)
{
// Exception was thrown as expected
}
CustomerAccount customerAccount =
customerAccountDAO.findById(3);
assertEquals("Incorrect customer account balance", 25,
customerAccount.getBalance(), 0);
Event event = eventDAO.findById(3);
assertEquals("Incorrect number of seats left", 250,
event.getAvailableSeats());
}
...
Execution of this unit test fails because the
purchaseTicket method fails to roll back the seat reservation when the
chargeCustomerAccount is unable to charge the customer due to an InsufficientFundsException. Here's the test output:
test:
[junit] Running
com.devx.ticketbroker.dao.impl.CustomerAccountDAOTest
[junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 0.688 sec
[junit] Running com.devx.ticketbroker.dao.impl.EventDAOTest
[junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 0.593 sec
[junit] Running com.devx.ticketbroker.service.impl.TicketServiceTest
[junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0.469 sec
BUILD FAILED
---
Excerpt from target/junit-reports/TEST-com.devx.ticketbroker.service.impl.TicketServiceTest.txt
---
Testcase: testPurchaseTicketInsufficientFunds took 0.031 sec
FAILED
Incorrect number of seats left expected:<250> but was:<248>