JavaRush /Java Blog /Random EN /What is AOP? Fundamentals of Aspect-Oriented Programming

What is AOP? Fundamentals of Aspect-Oriented Programming

Published in the Random EN group
Hello guys! Without understanding the basic concepts, it is quite difficult to delve into frameworks and approaches to building functionality. So today let's talk about one of these concepts - AOP, or aspect-oriented programming . What is AOP?  Fundamentals of Aspect Oriented Programming - 1This topic is not easy and is not often applied directly, but many frameworks and technologies use it under the hood. And of course, sometimes at interviews you may be asked to tell in general terms what kind of animal it is and where it can be used. So let's look at the basic concepts and some simple examples of AOP in Java . What is AOP?  Fundamentals of Aspect Oriented Programming - 2So AOP is Aspect Oriented Programming.is a paradigm that aims to increase the modularity of different parts of an application by separating cross-cutting concerns. To do this, additional behavior is added to the already existing code, without changing the original code. In other words, we seem to hang additional functionality on methods and classes from above, without making amendments to the modified code. Why is this needed? Sooner or later, we come to the conclusion that the usual object-oriented approach cannot always effectively solve certain problems. At such a moment, AOP comes to the rescue and gives us additional tools for building the application. And additional tools are an increase in flexibility during development, due to which there are more options for solving a particular problem.

Application of AOP

Aspect-oriented programming is designed to solve end-to-end problems, which can be any code that is repeated many times in different ways, which cannot be completely structured into a single module. Accordingly, with the help of AOP, we can leave this outside of the main code and define it vertically. An example is the application of a security policy in an application. Typically, security pervades many elements of an application. Moreover, the application's security policy should apply equally to all existing and new parts of the application. At the same time, the security policy used can develop itself. This is where the use of AOP can come in handy.. Also, logging can be given as another example . Using an AOP approach to logging has several advantages over manually inserting logging:
  1. The logging code is easy to implement and remove: all you need to do is add or remove a couple of configurations of some aspect.
  2. All source code for logging is stored in one place and there is no need to manually find all the places of use.
  3. The code intended for logging can be added anywhere, whether it be already written methods and classes or new functionality. This reduces developer errors.
    Also, when deleting an aspect from a design configuration, you can be absolutely sure that all tracing code is removed and nothing is missing.
  4. Aspects are separate code that can be reused and improved many times.
What is AOP?  Fundamentals of Aspect Oriented Programming - 3AOP is also used for exception handling, caching, removal of some functionality to make it reusable.

Basic concepts of AOP

To move further in the analysis of the topic, first we will get acquainted with the main concepts of AOP. Advice is additional logic, code that is called from the connection point. The advice can be executed before, after or instead of the connection point (more on them below). Possible types of advice :
  1. Before (Before) - tips of this type are launched before the execution of target methods - connection points. When using aspects as classes, we take the @Before annotation to mark the advice type as coming before. When using aspects as .aj files , this will be the before() method .
  2. After (After) - tips that are executed after the completion of the execution of methods - connection points, both in normal cases and when throwing an exception.
    When using aspects as classes, we can use the @After annotation to indicate that this is the advice that comes after.
    When using aspects as .aj files , this will be the after() method .
  3. After return (After Returning) - these tips are only executed when the target method works fine, without errors.
    When aspects are represented as classes, we can use the @AfterReturning annotation to mark the advice as executable upon successful completion.
    When using aspects as .aj files, this will be the after() method returning (Object obj) .
  4. After throwing (After Throwing) - this type of advice is intended for cases where a method, that is, a connection point, throws an exception. We can use this advice to handle some kind of failure (for example, to rollback the entire transaction or log with the required trace level).
    For class aspects, the @AfterThrowing annotation is used to indicate that this advice is used when an exception is thrown.
    When using aspects as .aj files , this will be the method - after() throwing (Exception e) .
  5. Around - perhaps one of the most important types of advice that surrounds a method, that is, a connection point, with which we can, for example, choose whether to execute a given connection point method or not.
    You can write advice code that runs before and after the join point method executes. Around advice
    is responsible for calling the join point method and returning values ​​if the method returns something. That is, in this tip, you can simply simulate the work of the target method without calling it, and return something of your own as a result. With aspects in the form of classes, we use the @Around annotation to create tips that wrap the connection point. When using aspects as .aj files
    it will be the around() method .
A join point is a point in the executing program (method invocation, object creation, variable access) where advice should be applied. In other words, this is some regular expression, with the help of which there are places for code injection (places for applying tips). Slice (pointcut) - a set of connection points . The slice determines whether a given connection point matches a given tip. Aspect is a module or class that implements end-to-end functionality. The aspect modifies the behavior of the rest of the code by applying the tip at join points defined by some slice . In other words, it is a combination of tips and connection points. Introduction (introduction) - changing the structure of the class and / or changing the inheritance hierarchy to add aspect functionality to foreign code. Target (target) - the object to which the tips will be applied. Weaving is the process of associating aspects with other objects to create the recommended proxy objects. This can be done at compile time, load time, or run time. There are three types of weaving:
  • compile-time weaving - if you have the source code for the aspect and the code in which you use aspects, you can compile the source code and the aspect directly using the AspectJ compiler;
  • post-compilation weaving (binary weaving) - if you cannot or do not want to use source code transformations to weave aspects into the code, you can take already compiled classes or jar files and inject aspects;
  • load-time weaving is just binary weaving, delayed until the class loader has loaded the class file and defined the class for the JVM.
    It requires one or more "weaving class loaders" to support this. They are either explicitly provided by the runtime or activated by the "weave agent.
AspectJ is a specific implementation of the AOP paradigms that implements the ability to solve end-to-end problems. Documentation can be found here .

Examples in Java

Next, for a better understanding of AOP, we will look at small examples of the Hello World level. What is AOP?  Fundamentals of Aspect Oriented Programming - 4I note right away that in our examples we will use compile-time weaving . First we need to write the following dependency in our pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
As a rule, a special Ajs compiler is used to use aspects . IntelliJ IDEA does not have it by default, so when choosing it as the application compiler, you must specify the path to the AspectJ distribution . You can read more about how to choose Ajs as a compiler on this page. This was the first way, and the second (which I used) is to write the following plugin in pom.xml :
<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 that, it is advisable to reimport from Maven and run mvn clean compile . Now let's move on to the examples.

Example #1

Let's create the Main class . In it we will have a launch point and a method that prints the names passed to it in the console:
public class Main {

  public static void main(String[] args) {
  printName("Толя");
  printName("Вова");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
Nothing complicated: we passed the name - we displayed it in the console. If we run now, the console will display:
Tolya Vova Sasha
Well, it's time to take advantage of the power of AOP. Now we need to create a file - aspect . They are of two types: the first is a file with the .aj extension , the second is a regular class that implements the capabilities of AOP using annotations. Let's look at the .aj file first :
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Привет ");
  }
}
This file is somewhat similar to a class. Let's figure out what's going on here: pointcut - a slice or a set of connection points; greeting() is the name of this slice; : execution - when executing * - all, call - Main.printName (..) - of this method. Next comes a specific advice - before() - which is executed before calling the target method, : greeting() - a slice to which this advice reacts, and below we see the method body itself, which is written in the Java language we understand. When running main with this aspect, we will get output to the console:
Hello Tolya Hello Vova Hello Sasha
We see that each call to the printName method has been modified with an aspect. And now let's take a look at how the aspect will look like, but already as a Java class with annotations:
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
After the .aj aspect file , everything is more obvious here:
  • @Aspect denotes that the given class is an aspect;
  • @Pointcut("execution(* Main.printName(String))") cutoff point that fires on all calls to Main.printName with an input argument of type String ;
  • @Before("greeting()") is a hint that applies before calling the code described in the greeting() cutpoint .
When running main with this aspect, the console output will not change:
Hello Tolya Hello Vova Hello Sasha

Example #2

Let's say we have some method that performs some operations for clients and call this method from main :
public class Main {

  public static void main(String[] args) {
  makeSomeOperation("Толя");
  }

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
Using the @Around annotation , we will do something like a “pseudo-transaction”:
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.makeSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
With the proceed method of the ProceedingJoinPoint object , we call the wrapper method to determine its place in the council and, accordingly, the code in the method above joinPoint.proceed(); is Before , which below is After . If we run main we get in the console:
Opening a transaction... Performing some operations for a client - Tolya Closing a transaction....
If we add an exception throw to our method (suddenly, the operation failed):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Then we get the output in the console:
Opening a transaction... Performing some operations for the client - Tolya Operation failed, transaction rolled back...
It turned out such a pseudo-processing of failure.

Example #3

As the next example, let's do something like logging in the console. First, let's look at Main , where we have pseudo business logic going on:
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое meaning>");
     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 , using setValue, we will set the value of the internal variable - value , then using getValue we will take this value and in checkValue we will check if this value is longer than 10 characters. If yes, an exception will be thrown. Now let's look at the aspect with which we will log the work of 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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
What's going on here? @Pointcut("execution(* *(..))") - will connect with all calls of all methods; @AfterReturning(value = "methodExecuting()", returning = "returningValue") is the advice that will be executed after the target method has successfully executed. We have two cases here:
  1. When a method has a return value if (returningValue != null) {
  2. When there is no return value else {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") is a tip that will fire on an error, that is, when an exception is thrown from the method. And accordingly, by running main , we will get a kind of logging in the console:
Successfully executed method - setValue, of class - Main Successfully executed method - getValue, of class - Main, with execution result - <some value> Method - checkValue, of class - Main, was terminated abnormally with exception - java.lang.Exception Method - main, class-Main, was aborted with exception - java.lang.Exception
Well, since we have not handled exceptions, we will still get its stack trace: What is AOP?  Fundamentals of Aspect Oriented Programming - 5You can read about exceptions and their handling in these articles: Exceptions in Java and Exceptions and their handling . That's all I have today. Today we met with AOP , and you could see that this beast is not as scary as it is painted. Goodbye everyone!What is AOP?  Fundamentals of Aspect Oriented Programming - 6What is AOP?  Fundamentals of Aspect Oriented Programming - 7
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION