ike most developers, I have been developing software systems using object-oriented programming (OOP) techniques for many years. So when I read that aspect-oriented programming (AOP) addresses many problems that traditional OOP doesn't solve completely or directly, I wanted to better understand its benefits in real world application development. I thought comparing both techniques would provide some practical insight. So I decided to design a case study: an OOP application in which I identified aspects where AOP might be a good fit.
This article presents a similar case study. It begins by introducing a problem domain and then demonstrates two solutions: one that uses OOP and one using AOP. The AOP solution uses a JDK 1.5, JUnit, and AspectWerkz implementation. (The complete source code for both approaches is available in the "Download the Code" section in the left-hand column.) Finally, it demonstrates how to code a few aspects.
By the end, you will know all you need to know about using AOP in the real world, including what it tries to solve in software design.
![]() | |
| Figure 1. Domain Model for the Solution |
Figure 1 illustrates the domain model for the solution to this problem.
The Manager class is derived from the Employee class, and it contains an additional attribute, Managing Project. A set of employees would be part of a business unit. The business units form the company. Ignore the Company class, since it is out of this problem domain's scope.
![]() | |
| Figure 2. Sequence Diagram of Object Interactions |
EmployeeServiceTestCase, a JUnit test case class, simulates an end user, creates a new employee record, and assigns him to a business unit and manager. It fetches all the available business units and managers to populate the GUI. The repository classes load data from storage. To instantiate the domain objects BusinessUnit and Manager, the retrieved record is passed to the factory classes. Later, an Employee object is created by passing these references to the EmployeeService object. The service class uses EmployeeFactory to create the object and passes it to EmployeeRepository for persisting in storage.
public static Set findAllBusinessUnits() throws RepositoryException {
Set businessUnits = new HashSet();
try {
FileReader businessUnitFile = null;
BufferedReader bufferedBusinessUnitFile = null;
try {
businessUnitFile = new FileReader(FILE_NAME);
bufferedBusinessUnitFile = new BufferedReader(businessUnitFile);
String businessUnitRecord;
while((businessUnitRecord = bufferedBusinessUnitFile.readLine()) != null) {
BusinessUnit businessUnit = BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
businessUnits.add(businessUnit);
}
} finally {
if(bufferedBusinessUnitFile != null) {
bufferedBusinessUnitFile.close();
}
if(businessUnitFile != null) {
businessUnitFile.close();
}
}
} catch(IOException ioe) {
String message = "IOError. Unable to find Business Unit records";
logger.log(SEVERE, message, ioe);
throw new RepositoryException(message, ioe);
}
logger.log(INFO, "Manager Records returned:" + businessUnits.size());
return businessUnits;
}
The code tries to read business unit records from a CSV file using FileReader and BufferedReader.
Applications repeatedly acquire a resource for an operation and then release it once the operation is complete. Removing this aspect of an application would improve the code readability and lead to a better design since once removed the remaining code does only what it is supposed to do. In this example, the method's objective is to read business unit records. So it doesn't need to worry about acquiring and releasing the necessary resources. Similarly, you also can handle the exceptions differently with AOP. (More on that later.)
public class EmployeeRepository {
public static void createEmployee(Employee employee) throws RepositoryException {
// Uses print writer to persist to the csv file.
}
public static String findEmployeeRecordById(String id) throws RepositoryException {
// Uses file reader to retrieve the employee record for the given id.
}
public static Employee findEmployeeById(String id) throws RepositoryException {
// Uses findEmployeeRecordById to fetch the employee record and the Employee
// object is created using the factory class.
}
public static void updateEmployee(Employee employee) {
// Updates the employee record.
}
}
The EmployeeService class uses a repository class to provide services related to employees in the application. In an enterprise application, removing persistence from the domain model improves the design. Modelers and programmers would be able to concentrate on the domain logic and handle persistence separately. You will see how this is achieved using AOP later.
public Employee createEmployee(String name,
String contactNumber,
BusinessUnit businessUnit,
Manager manager)
throws EmployeeServiceException {
String id = createNewEmployeeId();
Employee employee =
EmployeeFactory.createEmployee(id, name, contactNumber, businessUnit, manager);
try {
EmployeeRepository.createEmployee(employee);
} catch(RepositoryException re) {
String message = "Created employee successfully:" + employee;
logger.log(SEVERE, message);
throw new EmployeeServiceException(message, re);
}
logger.log(INFO, "Created employee successfully:" + employee);
return employee;
}
The above example includes a couple of audit logs, a fatal error, and a success message. The logging aspect can be moved out of the domain model and implemented separately.
Error handling varies case by case. Hence, applying AOP might differ for each of these scenarios.
![]() | |
| Figure 3. The Design of the AOP Approach |
The objective of the application is to fill the map in the BusinessUnitService class with BusinessUnit instances by reading the records from the CSV file. Using AOP to populate this map is like a backdoor approach, where the control is delegated to a repository class for reading BusinessUnit records from storage.
AOP is all about defining point cuts and advices. A point cut is a place of execution in the source code. The above example defined a point cut for the method findBusinessUnits in the class BusinessUnitService. An advice is a piece of code that executes when a point cut is reached. The BusinessUnitPersistentAspect class contains the advice method findAllBusinessUnits, which loads data from storage and uses the factory class to create the BusinessUnit object. This object is then added to the map. The map reference is retrieved from the BusinessUnitService instance. The point cuts and advices form an Aspect.
The OOP approach delegates the call to a DAO class in order to read from storage. With AOP, you define a point cut in the domain class and define an advice for reading from storage. The AOP framework would inject the code written as an advice, either at the time of execution (online weaving) or during compilation (offline weaving).
To summarize, when the call to findAllBusinessUnits in the BusinessUnitService class is made, the AOP framework injects code that is the advice method and pre-populates the map instance with the BusinessUnit instances by reading values from storage. This way, the persistence aspect of the application can be moved out of the domain model.
@Aspect("perJVM")
public class BufferedFileReaderAspect {
@Expression("execution(* org.javatechnocrats.aop.withaop.aspects.BusinessUnitPersistenceAspect.find*(..))")
Pointcut businessUnitPersistenceAspect;
// Other point cut definitions
@Expression("businessUnitPersistenceAspect ||
employeePersistenceAspect ||
managerPersistenceAspect")
Pointcut allPersistencePointcuts;
private Map<Class, String> fileNames;
public BufferedFileReaderAspect() {
System.out.println("BufferedFileReaderAspect created");
fileNames = new HashMap<Class, String>();
fillFileNames();
}
@Before("allPersistencePointcuts")
public void assignReader(JoinPoint joinPoint) throws Throwable {
System.out.println("assignReader advice called");
Object callee = joinPoint.getCallee();
IBufferedFileReaderConsumable bufReaderConsumable = (IBufferedFileReaderConsumable)callee;
Class persistenceClass = callee.getClass();
String fileName = fileNames.get(persistenceClass);
FileReader fileReader = new FileReader(fileName);
BufferedReader bufferedReader = new BufferedReader(fileReader);
bufReaderConsumable.setBufferedReader(bufferedReader);
}
@AfterFinally("allPersistencePointcuts")
public void releaseReader(JoinPoint joinPoint) throws Throwable {
// We would release the buffered reader resource
}
// Other methods
}
The code tries to create a point cut for all the method names, starting with find in the BusinessUnitPersistenceAspect class. Whenever these methods are called, the aspect class method assignReader is called before the find method executes. Here, it retrieves the callee class instance and sets the newly created buffered reader.
Similarly, in the releaseReader method, the code closes the buffered reader set previously. This section explains only the @before and @AfterFinally point cuts, which are defined using annotations in J2SE 5.0. Alternatively, you can declare them in the aspect definition XML file. You can examine the aop.xml file in the etc folder in the example code. (To get to know various types of point cuts, refer to the links in the Related Resources section in the left-hand column.)
@Aspect("perJVM")
public class BusinessUnitPersistenceAspect implements IBufferedFileReaderConsumable {
private BufferedReader buffFileReader;
@Before("execution(Collection org.javatechnocrats.aop.withaop.BusinessUnitService.findAllBusinessUnits())")
public void findAllBusinessUnits(JoinPoint joinPoint) throws Throwable {
System.out.println("findAllBusinessUnits advice called");
Map<String, BusinessUnit> businessUnits =
((BusinessUnitService)joinPoint.getThis()).getBusinessUnits();
String businessUnitRecord;
while((businessUnitRecord = buffFileReader.readLine()) != null) {
BusinessUnit businessUnit = BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
businessUnits.put(businessUnit.getId(), businessUnit);
}
}
public void setBufferedReader(BufferedReader buffFileReader) {
System.out.println("BusinessUnitPersistenceAspect.setBufferedReader called");
this.buffFileReader = buffFileReader;
}
public BufferedReader getBufferedReader() {
System.out.println("BusinessUnitPersistenceAspect.getBufferedReader called");
return this.buffFileReader;
}
}
The advice method reads the records from storage and uses the factory class to create BusinessUnit class instances. The created instances are then added to the Map, which handles the persistence aspect of the application.
@Aspect("perJVM")
public class LoggingAspect {
@Around("execution(String org.javatechnocrats.aop.withaop..*.toString())")
public Object toStringAdvice(JoinPoint joinPoint) throws Throwable {
System.out.println("toStringAdvice called");
String toString = (String)joinPoint.proceed();
Object target = joinPoint.getThis();
Field fields[] = target.getClass().getDeclaredFields();
List members = new ArrayList(fields.length + 1);
members.add(toString);
for(Field field : fields) {
field.setAccessible(true);
Object member = field.get(target);
members.add(field.getName() + "=" + member);
}
return members.toString();
}
You can use this sample code to complete the error handling aspect as well.
For AOP, follow these steps:
The following instructions walk you through executing the program:
set JAVA_HOME=c:\Program Files\Java\jdk1.5.0
set ASPECTWERKZ_HOME=C:\aw_2_0_2
set PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
set CLASSPATH=
C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar;
classes;C:\ junit\3.8.1\resources\lib\junit.jar
%ASPECTWERKZ_HOME%\bin\aspectwerkz -offline etc/aop.xml -cp classes classes
Hopefully, you also gained a new perspective on real world programming from this article. You should feel confident in using AOP to improve the design, modularity, and reusability of the code in your projects. At the very least, you can start using AOP for the logging, error-handling, and persistence aspects of applications.
Personally, I find the learning curve for AOP steep. Specifically, understanding the syntax for defining point cuts is a chore. Ideally, you would use OOP to design domain models and use AOP to move the concerns that cut across the domain classes out, hence making them neat and readable.
Another downside of AOP is debugging can be difficult, becauseunlike OOPthe program flow is not straightforward and the interactions are determined only at compile time or at runtime. I'm counting on some intelligent tools to address this issue in the near future.
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |