Aspect Oriented Programming and Annotations

October 02, 2016

Java  Desarrolladores 


Información: Si bien este blog está orientado a un público de habla hispana, de vez en cuando aparecerán entradas en inglés, debido a que es el lenguaje que los informáticos manejamos.

Disclaimer: This site is usually written in spanish, but a while ago, I had to prepare a document for a development group to introduce them into AOP and annotations, this group had english speakers. This is the document I prepared for them.

Over the development of a software, there are certain concerns that cut through multiple layers, types and objects of it, such as: Logging, Authenticating, Synchronizing, Validating, Exception Handling, etc. Such concerns are often called crosscutting concerns, and if you want to tackle the problem of handling them without code duplication and employing the DRY principle, you must handle them in some other way.

This is where AOP comes into hand. When speaking about object oriented programming, the modularity unit is classes; while on Aspect Oriented Programming, it is an aspect. In OOP, classes are created to group specific functionality related to a particular set of elements that share the same behavior. On AOP, such modules are called aspects. Aspects enable modularization of concerns without tampering with the general workflow of the code, leaving it as a much cleaner, organized, and easier to read.

For the sake of simplicity, we'll look at how AOP works by using Python decorators, which have a very similar functionality as Java's aspects, and also allows us to learn how another important part of this language works, which is annotations. Both of them are usually combined to deliver an even more powerful experience, and it's what is used in Spring AOP.

Annotations

Annotations have been a very important part of Java, and some of us might have seen annotations like @Override and @Deprecated in our application code at some place or another. One word to explain Annotation is Metadata. Metadata is data about data, and annotations are metadata for code.

Annotations do not alter the execution of a program, but the information embedded using annotations can be used by various tools during development and deployment.

Java annotations are typically used for the following purposes:

  • Compiler instructions
  • Build-time instructions
  • Runtime instructions

Java annotations can be be used at build-time, when you build your software project. The build process includes generating source code, compiling the source, generating XML files (e.g. deployment descriptors), packaging the compiled code and files into a JAR file etc. Building the software is typically done by an automatic build tool like Apache Ant or Apache Maven. Build tools may scan your Java code for specific annotations and generate source code or other files based on these annotations.

Normally, Java annotations are not present in your Java code after compilation. It is possible, however, to define your own annotations that are available at runtime. These annotations can then be accessed via Java Reflection, and used to give instructions to your program, or some third party API.

A Java annotation can have elements for which you can set values. An element is like an attribute or parameter. Here is an example of a Java annotation with an element:

@Entity(tableName = "vehicles")

You can place Java annotations above classes, interfaces, methods, method parameters, fields and local variables. The annotation starts with an @ character, followed by the name of the annotation. In this case, the annotation name is Entity and it doesn't have any meaning in Java. In this example, the annotation contains a single element named tableName, with the value set to vehicles. Elements are enclosed inside the parentheses after the annotation name. Annotations without elements do not need the parentheses.

An annotation can contain multiple elements. For example:

@Entity(tableName = "vehicles", primaryKey = "chasisNo")

In case an annotation contains just a single element, it is convention to name that element value, like this:

@InsertNew(value = "yes")

When an annotation just contains a single element named value, you can leave out the element name, and just provide the value:

@InsertNew("yes")

Java comes with three built-in annotations which are used to give the Java compiler instructions. These annotations are:

@Deprecated: It is used to mark a class, method or field as deprecated, meaning it should no longer be used. If your code uses deprecated classes, methods or fields, the compiler will give you a warning.

@Override: Used above methods that override methods in a superclass. If the method does not match a method in the superclass, the compiler will give you an error.

@SuppressWarnings: Makes the compiler suppress warnings for a given method. For instance, if a method calls a deprecated method, or makes an insecure type cast, the compiler may generate a warning, except if this annotation is present.

It is possible to create custom Java annotations, they must be defined in their own file, just like a Java class or interface, for example:

@interface Entity {
    String tableName();
    String primaryKey default “id”;
    String[] newNames();
}

Notice that it is also possible to define a default value for those elements, and that allows us to leave them out of the annotation declaration in our code.

The annotation declaration itself is annotated with the @Retention annotation. The @Retention annotation is used to specify the retention policy, which can be SOURCE, CLASS, or RUNTIME:

  • RetentionPolicy.SOURCE: retains an annotation only in the source file and discards it during compilation
  • RetentionPolicy.CLASS: stores the annotation in the .class file but does not make it available during runtime
  • RetentionPolicy.RUNTIME: stores the annotation in the .class file and also makes it available during runtime

Annotations can be queried at runtime using Reflection, but we won't look into that in this introduction, feel free to google it and see what it can be used to do.

You can specify which Java elements your custom annotation can be used to annotate. You do so by annotating your annotation definition with the @Target annotation.

The ElementType class contains the following possible targets:

ElementType.CONSTRUCTOR  
ElementType.FIELD  
ElementType.LOCAL_VARIABLE  
ElementType.METHOD  
ElementType.PACKAGE  
ElementType.PARAMETER  
ElementType.TYPE  

Annotations are very powerful and Frameworks like spring and Hibernate use Annotations very extensively for logging and validations. Annotations can be used in places where marker interfaces are used. Marker interfaces are for the complete class but we can define annotation which could be used on individual methods for example whether a certain method is exposed as service method or not.

Aspects

Basic concepts of Aspects

Aspect: A modularization of a concern that cuts across multiple objects. Transaction management is a good example of a crosscutting concern. In Spring AOP, aspects are implemented using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (@AspectJ style).

Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution. Join point information is available in advice bodies by declaring a parameter of type org.aspectj.lang.JoinPoint.

Advice: Action taken by an aspect at a particular join point. Different types of advice include "around," "before" and "after" advice. Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining a chain of interceptors "around" the join point.

Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP: Spring uses the AspectJ pointcut language by default.

Introduction: (Also known as an inter-type declaration). Declaring additional methods or fields on behalf of a type. Spring AOP allows us to introduce new interfaces (and a corresponding implementation) to any proxied object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching.

Target object: Object being advised by one or more aspects. Also referred to as the advised object. Since Spring AOP is implemented using runtime proxies, this object will always be a proxied object.

Weaving: Linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

Types of advice

Before: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).

AfterReturning: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception. AfterThrowing: Advice to be executed if a method exits by throwing an exception.

After (finally): Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).

Around: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

Around advice

It is the most general kind of advice. Since Spring AOP, like AspectJ, provides a full range of advice types, we recommend that you use the least powerful advice type that can implement the required behavior. For example, if you need only to update a cache with the return value of a method, you are better off implementing an after returning advice than an around advice, although an around advice can accomplish the same thing. Using the most specific advice type provides a simpler programming model with less potential for errors. For example, you do not need to invoke the proceed() method on the JoinPoint used for around advice, and hence cannot fail to invoke it.

The concept of join points, matched by pointcuts, is the key to AOP which distinguishes it from older technologies offering only interception. Pointcuts enable advice to be targeted independently of the Object-Oriented hierarchy. For example, an around advice providing declarative transaction management can be applied to a set of methods spanning multiple objects (such as all business operations in the service layer)

Declaring an aspect

With @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) will be automatically detected by Spring and used to configure Spring AOP. The following example shows the minimal definition required for a not-very-useful aspect:

A regular bean definition in the application context, pointing to a bean class that has the @Aspect annotation:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">  
   <!-- configure properties of aspect here as normal -->
</bean>  

And the NotVeryUsefulAspect class definition, annotated with org.aspectj.lang.annotation.Aspect annotation;

package org.xyz;  
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {}  

Aspects with @Aspect may have methods and fields just like any other class. They may also contain pointcut, advice, and introduction (inter-type) declarations. In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and excludes it from auto-proxying.

Declaring a pointcut

Recall that pointcuts determine join points of interest, and thus enable us to control when advice executes. Spring AOP only supports method execution join points for Spring beans, so you can think of a pointcut as matching the execution of methods on Spring beans. A pointcut declaration has two parts: a signature comprising a name and any parameters, and a pointcut expression that determines exactly which method executions we are interested in. In the @AspectJ annotation-style of AOP, a pointcut signature is provided by a regular method definition, and the pointcut expression is indicated using the @Pointcut annotation (the method serving as the pointcut signature must have a void return type). The following example defines a pointcut named 'anyOldTransfer' that will match the execution of any method named 'transfer':

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature  

Supported Pointcut Designators

Spring AOP supports the following AspectJ pointcut designators for use in pointcut expressions:

  • execution: For matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
  • within: Limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
  • this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type
  • target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type
  • args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
  • @target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type
  • @args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)
  • @within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)
  • @annotation: Limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation

Because Spring AOP limits matching to only method execution join points, the discussion of the pointcut designators above gives a narrower definition than you will find in the AspectJ programming guide. In addition, AspectJ itself has type-based semantics and at an execution join point both 'this' and 'target' refer to the same object - the object executing the method. Spring AOP is a proxy based system and differentiates between the proxy object itself (bound to 'this') and the target object behind the proxy (bound to 'target').

Combining pointcut expressions

Pointcut expressions can be combined using '&&', '||' and '!'. It is also possible to refer to pointcut expressions by name. The following example shows three pointcut expressions: anyPublicOperation (which matches if a method execution join point represents the execution of any public method); inTrading (which matches if a method execution is in the trading module), and tradingOperation (which matches if a method execution represents any public method in the trading module).

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}  

It is a best practice to build more complex pointcut expressions out of smaller named components as shown above. When referring to pointcuts by name, normal Java visibility rules apply (you can see private pointcuts in the same type, protected pointcuts in the hierarchy, public pointcuts anywhere and so on). Visibility does not affect pointcut matching.

Around advice

Since this kind of advice is what [The project] mostly uses, we'll be focusing our attention to it. Around advice runs "around" a matched method execution. It has the opportunity to do work both before and after the method executes, and to determine when, how, and even if, the method actually gets to execute at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner (starting and stopping a timer for example).

Around advice is declared using the @Around annotation. The first parameter of the advice method must be of type ProceedingJoinPoint. Within the body of the advice, calling proceed() on the ProceedingJoinPoint causes the underlying method to execute. The proceed method may also be called passing in an Object[] - the values in the array will be used as the arguments to the method execution when it proceeds.

import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

  @Around("execution(* com.xyz.myapp.*(...))")
  public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
  }
}

The value returned by the around advice will be the return value seen by the caller of the method. A simple caching aspect for example could return a value from a cache if it has one, and invoke proceed() if it does not. Note that proceed may be invoked once, many times, or not at all within the body of the around advice.