
Applying AOP
Aspect-oriented programming is designed to perform cross-cutting tasks, which can be any code that may be repeated many times by different methods, which cannot be completely structured into a separate module. Accordingly, AOP lets us keep this outside of the main code and declare it vertically. An example is using a security policy in an application. Typically, security runs through many elements of an application. What's more, the application's security policy should be applied equally to all existing and new parts of the application. At the same time, a security policy in use can itself evolve. This is the perfect place to use AOP. Also, another example is logging. There are several advantages to using the AOP approach to logging rather than manually adding logging functional:The code for logging is easy to add and remove: all you need to do is add or remove a couple of configurations of some aspect.
All the source code for logging is kept in one place, so you don't need to manually hunt down all the places where it is used.
Logging code can be added anywhere, whether in methods and classes that have already been written or in new functionality. This reduces the number of coding errors.
Also, when removing an aspect from a design configuration, you can be sure that all the tracing code is gone and that nothing was missed.
- Aspects are separate code that can be improved and used again and again.

Basic principles of AOP
To move further in this topic, let's first get to know the main concepts of AOP. Advice — Additional logic or code called from a join point. Advice can be performed before, after, or instead of a join point (more about them below). Possible types of advice:Before — this type of advice is launched before target methods, i.e. join points, are executed. When using aspects as classes, we use the @Before annotation to mark the advice as coming before. When using aspects as .aj files, this will be the before() method.
- After — advice that is executed after execution of methods (join points) is complete, both in normal execution as well as when throwing an exception.
When using aspects as classes, we can use the @After annotation to indicate that this is advice that comes after.
When using aspects as .aj files, this is the after() method.
After Returning — this advice is performed only when the target method finishes normally, without errors.
When aspects are represented as classes, we can use the @AfterReturning annotation to mark the advice as executing after successful completion.
When using aspects as .aj files, this will be the after() returning (Object obj) method.
After Throwing — this advice is intended for instances when a method, that is, join point, throws an exception. We can use this advice to handle certain kinds of failed execution (for example, to roll back an entire transaction or log with the required trace level).
For class aspects, the @AfterThrowing annotation is used to indicate that this advice is used after throwing an exception.
When using aspects as .aj files, this will be the after() throwing (Exception e) method.
Around — perhaps one of the most important types of advice. It surrounds a method, that is, a join point that we can use to, for example, choose whether or not to perform a given join point method.
You can write advice code that runs before and after the join point method is executed.
The around advice is responsible for calling the join point method and the return values if the method returns something. In other words, in this advice, you can simply simulate the operation of a target method without calling it, and return whatever you want as a return result.
Given aspects as classes, we use the @Around annotation to create advice that wraps a join point. When using aspects in the form of .aj files, this method will be the around() method.
Compile-time weaving — if you have the aspect's source code and the code where you use the aspect, then you can compile the source code and the aspect directly using the AspectJ compiler;
Post-compile weaving (binary weaving) — if you cannot or do not want to use source code transformations to weave aspects into the code, you can take previously compiled classes or jar files and inject aspects into them;
Load-time weaving — this is just binary weaving that is delayed until the classloader loads the class file and defines the class for the JVM.
One or more weaving class loaders are required to support this. They are either explicitly provided by the runtime or activated by a "weaving agent."
Examples in Java
Next, for a better understanding of AOP, we will look at small "Hello World"-style examples. Right of the bat, I'll note that our examples will use compile-time weaving. First, we need to add the following dependency in our pom.xml file:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
As a rule, the special ajc compiler is how we use aspects. IntelliJ IDEA doesn't include it by default, so when choosing it as the application compiler, you must specify the path to the 5168 75AspectJ distribution.
This was the first way. The second, which is the one I used, is to register the following plugin in the pom.xml file:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<<verbose>true<verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
After this, it's a good idea to reimport from Maven and run mvn clean compile.
Now let's proceed directly to the examples.Example No. 1
Let's create a Main class. In it, we will have an entry point and a method that prints a passed name on the console:
public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
There's nothing complicated here. We passed a name and display it on the console. If we run the program now, we'll see the following on the console:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
This file is kinda like a class. Let's see what is happening here:
pointcut is a set of join points;
greeting() is the name of this pointcut;
: execution indicates to apply it during the execution of all (*) calls of the Main.printName(...) method.
Next comes a specific advice — before() — which is executed before the target method is called. : greeting() is the cutpoint that this advice responds to. Well, and below we see the body of the method itself, which is written in the Java language, which we understand. When we run main with this aspect present, we will get this console output:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
After the .aj aspect file, everything becomes more obvious here:- @Aspect indicates that this class is an aspect;
- @Pointcut("execution(* Main.printName(String))") is the cutpoint that is triggered for all calls to Main.printName with an input argument whose type is String;
- @Before("greeting()") is advice that is applied before calling the code specified in the greeting() cutpoint.
Example No. 2
Suppose we have some method that performs some operations for clients, and we call this method from main:
public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
Let's use the @Around annotation to create a "pseudo-transaction":
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
With the proceed method of the ProceedingJoinPoint object, we call the wrapping method to determine its location in the advice. Therefore, the code in the method above joinPoint.proceed(); is Before, while the code below it is After.
If we run main, we get this in the console:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
Then we get this console output:
Example No. 3
In our next example, let's do something like logging to the console. First, take a look at Main, where we've added some pseudo business logic:
public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
In main, we use setValue to assign a value to the value instance variable. Then we use getValue to get the value, and then we call checkValue to see if it is longer than 10 characters. If so, then an exception will be thrown.
Now let's look at the aspect we will use to log the work of the methods:
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
What's going on here?
@Pointcut("execution(* *(..))") will join all calls of all methods.
@AfterReturning(value = "methodExecuting()", returning = "returningValue") is advice that will be executed after successful execution of the target method.
We have two cases here:- When the method has a return value — if (returningValue! = Null) {
- When there is no return value — else {


GO TO FULL VERSION