AOP - Aspect-Oriented Programming

Introduction

While building application programs, there are many software-related issues that we need to take care of. For example, we build an account registration system for a bank. In addition to the main job of allowing users to create accounts (core concern), the system also has to ensure other issues (cross-cutting concerns) such as user authentication, validation, transaction management, Exception handling, logging, debugging, measuring application performance, etc.

Logger logger = Logger.getLogger(...);
TransactionManager tm = getTransactionManager();
public void addAccount(Account account) {
    logger.info("Creating (" + account + ") Account");
    try {
        tm.beginTransaction();
        db.add(account);
        tm.commit();
    } catch (Exception) {
        tm.rollback();
        logger.error("Account creation failed");
    }
}

As you can see, the logic of our program has to do a lot of things like logging, opening/closing transactions, handling exceptions, etc. When there are many similar methods in the application, our code is tightly coupled into each other, duplicates code, fragments in many places, difficult to modify and add new logic, etc. To solve this problem, we can apply AOP.

What is AOP?

Aspect Oriented Programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.

It does this by adding additional behavior to existing code without modifying the code itself. We can declare the new code and the new behaviors separately.

AOP also known as Aspect-Oriented Software Development (AOSD) is a design principle that helps to separate concerns of the program into independent components and thereby increase program flexibility. In the Separation of concerns, it is argued that similar concerns should be addressed in a unit of code. In functional programming, a unit of code is a function/method, while in object-oriented programming (OOP), the unit of code is a class.

In AOP, our program is divided into two types of concerns:

  • Core concern/ Primary concern : is the requirement, the main processing logic of the program.

  • Cross-cutting concern : are the extra processing logic that needs to be performed by the program when the core concern is called such as security, logging, tracing, monitoring, etc.

Some common cross-cutting concerns in applications:

  • Logging

  • Monitoring

  • Authentication

  • Access control

  • Validation

  • Error handling

  • Transaction management

  • Session management

AOP Concepts and Terminology

Joinpoint

Joinpoint is a point in the program where cross-cutting concerns can be inserted. For example, we need to log after running a certain method, the point immediately after that method is executed is called a Jointpoint. A Jointpoint can be the execution of a method or the handling of an exception.

In Spring AOP, a JoinPoint always represents a method execution.

Pointcut

There are many ways to define Joinpoint, such ways are called Pointcut. It is expressions used to check it matches Jointpoints to determine if Advice needs to be executed.

Advice

Crosscutting concerns are added to the core concern, the code to perform those actions is called Advice. Advice is divided into the following categories:

  • Before: is executed before the join point.

  • After: is performed after the join point.

  • Around: performed before and after the join point.

  • After returning : is performed after the join point is completed normally.

  • After throwing : is performed after the join point is terminated with an Exception.

Aspect

Similar to a Java class. An Aspect encapsulates the entire cross-cutting concern and can contain JointPoint, PointCut, Advice.

Target Object

Target object are the objects to which the advice is applied.

What's Weaving?

Basically, Weaving is the process of linking the aspect and non-aspect components of a program to produce the desired output.

There are a few variations between AOP systems in terms of how Weaving is generated. Weaving can be divided into types of Weaving: Compile-time weaving (static weaving), Load-Time Weaving and Run-time weaving (dynamic weaving).

  • Compile-time weaving :

    • Pre-Compile Weaving : uses a pre-processor to combine aspect code and non-aspect code together before the code is compiled into Java bytecode (.class).

    • Post-Compile Weaving / Binary weaving : this is used to inject the aspect's code into the compiled Java .class files.

  • Load-Time Weaving : this method is used to inject the code of the aspect when the class that needs to use the aspect is loaded into the JVM, ie while the application is running.

  • Run-time weaving : performs weaving and unweaving code of aspect and non-aspect at run-time.

Static weaving

Static weaving is the process of combining aspect code and non-aspect code together before the code is compiled into Java byte code (.class) using a pre-processor. Therefore, the original code is changed only once at compile time. The performance of this combined code is comparable to that of the traditionally written code.

The limitation of the Static weaving method is the difficulty in determining the aspect's code later on or making changes to the aspect's code. Every time an aspect's code is changed, all code that uses the aspect must be recompiled.

Dynamic weaving

Dynamic weaving overcomes some of the limitations encountered when weaving is done at compile time (Compile-time weaving/ Static weaving). Recompilation, redeployment, and restart requirements can be avoided by performing run-time weaving.

There is a slight difference between load-time and run-time weaving.

  • Load-time weaving simply delays the weaving until the classes are loaded by the class loader. This approach requires using a weaving class loader or replacing the class loader with another loader. Drawbacks are increased load-time and lack of access to aspects while running. To support this, one or more “weaving class loaders” are required. These are either provided explicitly by the run-time environment or enabled using a “weaving agent”.

  • Run-time weaving is the process of weaving and unweaving at run-time. This approach requires new mechanisms to interfere with running computations.

Different AOP Frameworks have different ways of doing dynamic weaving. While AspectWerkz uses bytecode modification through JVM level functionality and “hotswap” architecture to do the weaving of classes at run-time, Spring AOP Framework relies on proxies instead of class loaders or relies on JVM arguments.

Dynamic weaving allows to speed up the design and testing phases of software development, as new aspects can be added or existing aspects can be changed without recompiling and redeploying the components, application. However, a major drawback is reduced performance, since weaving occurs at run-time.

How to implement AOP in Java?

Some ideas to implement AOP in our program:

  • Class-weaving : as mentioned above.

  • Proxy-based : you can imagine it as an example using the Decorator Pattern . Using the bytecode encoding tool of some libraries like JDK proxy, CGLib proxy, we can intercept function calls and add our own code to be executed first.

    • JDK proxy : This is the simplest way, but can only deal with public methods being called, can't handle internal calls (calls derived from the class itself).

    • CGLib proxy : CGLib requires limited bytecode editing and can handle private method calls , but still cannot handle direct property access.

  • Use the following libraries: Spring AOP, AspectJ, Google Guice.

Spring AOP and AspectJ

Spring AOP and AspectJ have different goals:

  • Spring AOP aims to provide a simple AOP implementation across Spring IoC to solve the most common problems that programmers face. It is not intended as a complete AOP solution – it can only be applied to beans that are managed by a Spring container.

  • On the other hand, AspectJ is the original AOP technology which aims to provide complete AOP solution. It is more robust but also significantly more complicated than Spring AOP. It's also worth noting that AspectJ can be applied across all domain objects.

Both AspectJ and Spring AOP uses the different type of weaving which affects their behavior regarding performance and ease of use.

  • AspectJ makes use of three different types of weaving:

    • Compile-time weaving: The AspectJ compiler takes as input both the source code of our aspect and our application and produces a woven class files as output

    • Post-compile weaving: This is also known as binary weaving. It is used to weave existing class files and JAR files with our aspects

    • Load-time weaving: This is exactly like the former binary weaving, with a difference that weaving is postponed until a class loader loads the class files to the JVM

  • As AspectJ uses compile time and classload time weaving, Spring AOP makes use of runtime weaving.

Spring AOP is a proxy-based AOP framework. This means that to implement aspects to the target objects, it'll create proxies of that object. This is achieved using either of two ways:

  • JDK dynamic proxy: the preferred way for Spring AOP. Whenever the targeted object implements even one interface, then JDK dynamic proxy will be used

  • CGLIB proxy: if the target object doesn't implement an interface, then CGLIB proxy can be used

AspectJ, on the other hand, doesn't do anything at runtime as the classes are compiled directly with aspects.

Examples

Refer to: Building an AOP framework

What are the benefits of AOP?

  • Increase the efficiency of Object-Orented Programming (OOP).

  • AOP is not meant to replace OOP but to complement OOP, where OOP is lacking in creating complex applications.

  • Maximize reusability of source code.

  • Ensure Single Responsibility Principle : each module only does what it needs to do.

  • Ensure DRY (Do Not Repeat Yourself) principle: no code duplication.

  • Adhering to the “You aren't gonna need it – YAGNI ” principle – we only install the things we really need, never before.

  • Modularization at the process/function level.

  • Code is cleaner due to separation of main processing and related processing.

  • The main function of the program does not need to know the other functions.

  • Additional functions can be added, turned on or off at run-time depending on requirements.

  • Changes, if any, to the secondary functions will not affect the main program.

  • The system will be flexible and reduce the interdependence of modules.

References

Last updated