Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Using Annotations with Aspects in Pre-Java 5 Versions : Page 2

If you're interested in combining the power of annotations and aspects but can't yet move to Java 5, don't be discouraged; you still have robust options. Several AOP frameworks provide this capability today.


advertisement
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 Annotations—Type safe annotations are typically defined as an interface or JavaBean and expose allowable attributes as properties and constructor arguments.
  • Untyped Annotations—Some frameworks allow you to access javadoc metadata without having to pre-define your annotations.
  • Named Parameters—An annotation configuration that uses name-value pairs.
  • Anonymous Parameters—An annotation configuration that uses single values or a comma-separated list.
  • Target Restriction—The process of restricting annotations to elements of a particular type such as classes, methods, or fields.
  • Annotation Inheritance—A term meaning that annotations defined on superclasses are available on subclasses.
  • Annotation Storage—A scheme for storing metadata so that it can be made available at runtime.
  • Ant Task—Determine whether the framework provides an Ant task to process javadoc metadata.
  • Maven Plugin—Determine 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 Application—Ticket 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 applied—a 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 three—the 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>



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap