I want my AOP!, Part 3

Use AspectJ to modularize crosscutting concerns in real-world problems

1 2 3 Page 3
Page 3 of 3

Example 3: Characteristic-based implementation modularization

Operations with the same characteristics should typically implement common behaviors. For example, a wait cursor should be put before any slow method executes. Similarly, you may need to authenticate access to all security-critical data.

Since such concerns possess a crosscutting nature, AOP and AspectJ offer mechanisms to modularize them. Because a method's name might not indicate its characteristics, you need a different mechanism to capture such methods. To create such a mechanism, declare the aspect adding characteristic-based crosscutting behavior as an abstract aspect. In that aspect, declare an abstract pointcut for methods with characteristics under consideration. Finally, write an advice performing the required implementation.

Consider SlowMethodAspect's implementation. It declares the abstract slowMethods() pointcut and advises it to first put a wait cursor, proceed with the original operation, and finally restore the original cursor, as Figure 4 shows.

Figure 4. The aspect structure for crosscutting characteristics-based concerns. Click on thumbnail to view full-size image.

Here's the code:

// SlowMethodAspect.java
import java.util.*;
import java.awt.*;
import java.awt.event.*;
public abstract aspect SlowMethodAspect {
    abstract pointcut slowMethods(Component uiComp);
    void around(Component uiComp) : slowMethods(uiComp) {
        Cursor originalCursor = uiComp.getCursor();
        Cursor waitCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
        uiComp.setCursor(waitCursor);
        try {
            proceed(uiComp);
        } finally {
            uiComp.setCursor(originalCursor);
        }
    }
}

Two test components, GUIComp1 and GUIComp2, nest a concrete implementation of the aspect. For example, GUIComp1 contains following aspect:

public static aspect SlowMethodsParticipant extends SlowMethodAspect {
pointcut slowMethods(Component uiComp) 
            : execution(void GUIComp1.performOperation1())
            && this(uiComp);
}

Whereas, GUIComp2 contains following aspect:

public static aspect SlowMethodsParticipant extends SlowMethodAspect {
        pointcut slowMethods(Component uiComp) 
            : (execution(void GUIComp2.performOperation1())
               || execution(void GUIComp2.performOperation2()))
            && this(uiComp);
}

Compared to this article's other examples, in this example the aspected classes now no longer remain oblivious to other aspects; they include code to participate in the collaboration. However, such knowledge is limited -- it merely declares a method with certain known characteristics. Also note, you need not implement SlowMethodsParticipant as a nested aspect. However, since these aspects completely depend on the surrounding classes, making them nested aspects helps keep the pointcut coordinated with implementation changes.

That usage pattern lets you provide aspectual class interfaces and lets you capture semantics- and characteristics-based pointcuts not otherwise possible by property-based pointcuts.

You'll find the complete code for both test components in GUIComp1.java and GUIComp2.java, and a simple test application in Test.java (see Resources).

Example 4: Implement flexible access control

With AspectJ you can declare compile-time warnings and errors, a mechanism with which you can enforce static crosscutting concerns. For example, such a mechanism can enforce an access-control crosscutting concern. Java's access controls -- public, private, package, and protected -- do not offer enough control in many cases. Consider, for example, a typical Factory design-pattern implementation. Oftentimes, you want only the factory to create the product objects. In other words, classes other than the factory should be prohibited from accessing constructors of any product class. While marking constructors with package access offers the best choice, that works only when the factory resides in the same package as the manufactured classes -- quite an inflexible solution.

C++'s friend mechanism controls access to a class from other specified classes and methods. Similarly, with AspectJ you can implement such functionality in Java, as well as a much more fine-grained and expressive access control.

The following code declares a simple Product class with a constructor and a configure() method. It also declares a nested FlagAccessViolation aspect, which in turn declares one pointcut to detect constructor calls from classes other than ProductFactory or its subclasses, and another pointcut to detect configure() method calls from classes other than ProductConfigurator or its subclasses. It finally declares either such violations as compile-time errors. ProductFactory, ProductConfigurator, or its subclasses could reside in any package, and both pointcuts could specify packages if necessary. You can further restrict access to, say, the createProduct() method, using the withincode() pointcut instead of within:

// Product.java
public class Product {
    public Product() {
        // constructor implementation
    }
    public void configure() {
        // configuration implementation
    }
    static aspect FlagAccessViolation {
        pointcut factoryAccessViolation() 
            : call(Product.new(..)) && !within(ProductFactory+);
        
        pointcut configuratorAccessViolation() 
            : call(* Product.configure(..)) && !within(ProductConfigurator+);
        declare error 
            :  factoryAccessViolation() || configuratorAccessViolation() 
            : "Access control violation";
    }
}

The ProductFactory class calls the Product.configure() method, thus causing a compile-time error with a specified "Access control violation" message:

// ProductFactory.java
public class ProductFactory {
    public Product createProduct() {
        return new Product();
    }
    
    public void configureProduct(Product product) {
        product.configure();
    }
}

The rest in short

In this section, I outline a few simple, yet powerful AspectJ usages. You may implement these examples as part of your AspectJ learning process.

Logging and debugging

You can easily try a logging and debugging project in AspectJ. Using AspectJ for logging and debugging also lets you log different contextual information without touching the main code.

Design by contract

Design by contract (DBC) requires explicit contracts that must hold true at various execution points, such as before and after each operation. You can use AspectJ to enforce DBC by creating aspects containing advices to check contracts at required execution points. If you wish to not enforce contracts in a production environment, simply exclude DBC aspects from your production build.

Realize design patterns

AOP allows interesting twists when you implement certain design patterns. For example, AOP lets you look at MVC in a new way. Without AOP, MVC models are responsible for three concerns: state management, listener management, and state update notification. With AOP and AspectJ, you modularize each concern, which also lets you use models originally written only for state management, for example, Date or Point2D in MVC.

Lazy creation/initialization

It's common to optimize memory-intensive objects by lazily creating them. As a result, they exist just in time to serve for the first time. As with other optimizations, you'd use lazy instantiation because of profiling. However, it's invasive to change your implementation everywhere to lazily create certain fields. At the most, OOP only can require programmers to always use a get<Field>() method and modularize logic for "create-if-needed" in that method. With that approach, if a programmer forgets to use this method and does a direct access, a crash awaits. Marking such fields with private access does not help for in-class field access. Even if you discover the need earlier during the design phase and the problem mentioned above is unimportant, it is nice to separate optimization concerns to avoid overloading the main implementation logic.

AspectJ's get() pointcut lets you capture access to a field. You can advise such a pointcut to create an object if the field was null. With this, the logic of checking for null and creating an object if needed is modularized.

Caching

High performance applications need to cache expensive-to-create objects. For example, a server serving images, say stock charts, may need to cache byte-stream image representations. AspectJ can help separate caching concern implementations. With such a scheme, you capture image creation joinpoints, looking into the cache for an available image for some given data, then using the cached image instead. If no such image is available, the advice to such joinpoints creates an image and stores it in a hash table. The caching implementation could also employ a soft reference or some other scheme to ensure cached images do not cause the application to starve for memory. By using AspectJ, you can make noninvasive changes if the caching logic needs to change.

Parting thoughts

Be warned before reading this section as it represents my opinionated view of the world!

AOP and AspectJ solve real problems, and early adopters are increasingly trying AOP concepts with AspectJ. Like any other new programming paradigm, it will take time before both technologies reach their full potential. As more developers use AspectJ, the experience gained will guide the language's evolution. Simultaneously, design patterns and anti-patterns will appear. AOP's adaptation as a mainstream programming methodology will be a fun thing to watch and, better still, participate in.

I want to thank Kavita Laddad and Rick Warren for reviewing this series.

Ramnivas Laddad is a Sun Certified Architect of Java Technology. He holds a master's degree in electrical engineering with a specialization in communication engineering. He has been architecting, designing, and developing software projects involving GUIs, networking, distributed systems, real-time systems, and modeling for more than eight years. Ramnivas began working with object-oriented Java systems five years ago. As a principal software engineer at Real-Time Innovations, Inc., Ramnivas leads the development of the next generation of ControlShell, a component-based programming framework for building complex real-time systems.

Learn more about this topic

Related:
1 2 3 Page 3
Page 3 of 3