Example


Let's start with an example of how we can use Spring's AOP support to provide a simple, effective solution to a common problem.

Let's assume that we have a business interface called AccountManager, and that one of the non-functional requirements is "Email the system administrator when any exception is thrown by a business method, letting the exception propagate to the layer above to handle it."

Note 

Thanks to Spring developer Dmitriy Kopylenko for contributing the following example and some of the UML diagrams in this chapter.

Let's take a look at the implementation of this requirement without the help of AOP. Following OO best practice, we'll have an AccountManager interface, but as it can easily be deduced from the following implementation class, we won't show it. The implementation class is configured by Setter Injection:

public class AccountManagerImpl implements AccountManager {     private MailSender mailSender;     private SimpleMailMessage message;     private AccountDao accountDao;     public void setMailSender(MailSender mailSender) {         this.mailSender = mailSender;     }         public void setMessage(SimpleMailMessage message) {         this.message = message;     }       public void setAccountDao(AccountDao accountDao) {       this.accountDao = accountDao;   }     private void sendMail(Exception ex) {         SimpleMailMessage msg = new SimpleMailMessage(this.message);         msg.setText("Encountered exception " + ex.getMessage());         this.mailSender.send(msg); }         public Account getAccount(String accountId) throws AccountNotFoundException, DataAccessException {         try{             return this.accountDao.findAccount(accountId);         }         catch(AccountNotFoundException ex){             sendMail(ex);             throw ex;         }         catch (DataAccessException ex) {             sendMail(ex);             throw ex;         }     }         public void createAccount(Account account) throws DataAccessException,  {            try{                 if (isInvalid(account)) {                     throw new InvalidAccountException(account); }                 this.accountDao.saveAccount(account);             }         catch (IOException ex) {             sendMail(ex);             throw ex;         }             catch (DataAccessException ex) {             sendMail(ex);             throw ex;         }         } }

As you can see, the "boilerplate" code of the try-catch block to send email is scattered throughout the code base in different business methods such as getAccount() and createAccount(). Although the actual sending of email can be extracted into the sendMail() method, the invocation of this method crosscuts numerous methods.

The notification functionality is not the main responsibility of the class and is effectively a "crosscutting" behavior, yet it has a major impact on how the class is implemented. The solution is also inelegant. Consider the following problems:

  • What if we add more methods requiring exception logging? We would need to remember to add the boilerplate code to them also. It is no longer possible to add or maintain code in this class without considering the exception notification concern.

  • We need a distinct catch block for every checked exception subclass. Although we really are interested only in the fact that an exception was thrown, not the class of the exception, we need a distinct block for each type of exception to preserve method signatures. While we could catch Exception or even Throwable with a single catch block, we could not rethrow to propagate them to callers without breaking the throws clause of each method signature.

  • What if business requirements change, so that exception notification is not required, or a completely different strategy is required? As exception notification is baked into this class, this could be problematic.

These problems are likely to be magnified many times, as the exception notification policy will need to be enforced not just in one business object, but across the entire code base.

In short, we have a serious problem for code quality and maintainability, which pure OO cannot solve.

The solution is AOP, which enables us to modularize the crosscutting exception notification concern and apply it to any number of methods on any number of objects.

Let's refactor the preceding email notification code into a modular aspect and apply it to our AccountManager using Spring AOP. We begin by writing an aspect class that implements the exception notification policy:

public class EmailNotificationThrowsAdvice implements ThrowsAdvice {     private MailSender mailSender;       private SimpleMailMessage message;       public void setMailSender(MailSender mailSender) {        this.mailSender = mailSender;   }     public void setMessage(SimpleMailMessage message) {      this.message = message;   }       public void afterThrowing(Exception ex) throws Throwable {       SimpleMailMessage msg = new SimpleMailMessage(this.message);       msg.setText("Encountered exception " + ex.getMessage());       this.mailSender.send(msg);   }      }

We can then apply this advice to multiple objects. Following normal Spring practice, we define the advice itself as an object in a Spring IoC container, using Dependency Injection:

<bean          >       <property name="mailSender"><ref bean="mailSender"/></property>       <property name="message"><ref bean="mailMessage"/></property> </bean>

We can then apply the advice to any number of other objects managed by the Spring container, as follows:

<bean        >     <property name="beanNames"><value>accountManagerTarget</value></property>     <property name="interceptorNames">         <list>             <value>emailNotificationThrowsAdvice</value>         </list>     </property> </bean>

Don't worry too much about the details, which we'll explain later. This is one of several ways Spring offers to apply advice to multiple managed objects.

Now we have the pleasant task of removing all the crosscutting code in AccountManagerImpl concerned with exception notification:

public class AccountManagerImpl implements AccountManager {         private AccountDao accountDao;         public void setAccountDao(AccountDao accountDao) {         this.accountDao = accountDao;     }         public Account getAccount(String accountId)                      throws AccountNotFoundException, DataAccessException {         return this.accountDao.findAccount(accountId);     }         public void createAccount(Account account) throws DataAccessException {         this.accountDao.saveAccount(account);     } }

The result is a major improvement. The AccountManager is purely responsible for account management, making the code much simpler and easier to read and maintain. The exception notification mechanism is cleanly modularized so that it can be maintained separately from business logic. For example, one developer can now revise the exception notification policy without the need to modify large numbers of files that would be affected in the traditional approach.

There is some complexity in setting up the AOP framework, but the payoff is an even greater return if there are more crosscutting concerns to consider. For example, what if any of these methods should be transactional? With the AOP infrastructure in place, it's easy to add a transaction aspect.

We hope you can now see the value proposition in AOP and Spring's integration of AOP with its IoC container. In the rest of this chapter, we'll look at the capabilities of Spring AOP in detail and how you can use other AOP frameworks with Spring.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net