AOP:Aspect-Oriented-Programming Overview

Last Updated on: February 8, 2021 pm

AOP Overview

AOP, at its core, lets you implement individual concerns in a loosely coupled fashion, and combine these implementations to form the final system. Indeed, AOP creates systems using loosely coupled, modularized implementations of crosscutting concerns. OOP, in contrast, creates systems using loosely coupled, modularized implementations of common concerns.

AOP Development Steps

Aspectual Decomposition

Identify Cross-cutting Concerns from common concerns.

There are two kinds of concerns: module-level concerns and crosscutting system-level concerns.

For example, in a credit card payment system, there should be three modules:

  • Core Credit Card Payment Process - Module level
  • Logging Module - Crosscutting
  • Authentication - Crosscutting

Concern Implementation

In this step, you should implement each concern seperately.

Aspectual Recomposition

You need an aspect integrator to specify recomposition rules by creating aspects. The recomposition is known as weaving or integrating. This infomation is used to integrate the modules of the final system.

Terms in AOP

Cross-Cutting Concerns

Concerns = Functionality = Logic

Example: Loggin Concern. It always crosscuts every module in the system level. In other words, it should be applied to every module that needs loggin function.

Meanwhile, the normal concern’s implementation remains unware that other crosscutting concerns logging is “aspecting” it. For exanmple, the credit card processing module doesn’t know that other concerns are logging or authenticating its operations. The crosscutting concerns are like a proxy or some good spies that are monitoring on the operations and maintaining security of the entire system.

Proxy Design Pattern

通过代理,控制对对象的访问。

定义一个抽象角色,让代理角色和真实角色分别去实现它。

  • 真实角色 - implements AbstractRole 定义真正的业务逻辑,供代理角色使用。
  • 代理角色 - implements AbstractRole 通过真实角色的业务逻辑来实现抽象方法,并在抽象方法前后附加自己的代理操作。

Spring AOP 采用JDK动态代理和CGLIB动态代理。

如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。

Weaving

The weaver assembles an individual concerns in the process of weaving.

In other words, the weaver interlaces different fragments based on some rules - weaving rules - applied to it.

Compile-time

AspectJ is based on Bytecode Manipulation.

Load-time

AspectJ

Run-time

Slowest.

Spring AOP - minor performance cost for aspect execution because of run-time weaving. It is based on Proxying.

Aspects

Module of code for cross-cutting concerns. (Eg. logging, transaction, security, …)

Advice

What action is taken and when it should be applied.

Advice defines pieces of an aspect implementation to be executed at pointcuts

Before

run before the method

After finally

run after the method - just like the Finally in try-catch block.

After returning

run after the method’s successful execution

After throwing

run after the method throws an exception.

Around

run before and after the method.

Join Points

Join points define specific points in a program’s execution.

It refers when to apply the code during the whole execution.

Point Cuts

A pointcut is the language construct that specifies join points.

A predicate expression for where the advice should be applied.

Benefits of AOP

AOP helps overcome the aforementioned problems caused by code tangling and code scattering.

  1. Modularized implementation of crosscutting concerns. - Code of Aspects is defined in a single class. AOP minimizes coupling and addresses cross-cutting concerns seperately. In general, it represents a higher reused of the Aspect code.
  2. Easier-to-evolve systems - Easier to add new aspect because the aspected modules are unaware of crosscutting concerns. And when you add a new functionality, the existinig aspects will also crosscut it.

Advice Type

@Before Advice Type

  1. Create a Target Object: AccountDAO.java

    1
    2
    3
    4
    5
    6
    @Component
    public class AccountDAO {
    public void addAccount(){
    System.out.println(getClass() + " : DOING MY DB WORK : ADDING ACCOUNT");
    }
    }
  2. Create a spring Java Config class - Pure Java Code

    1
    2
    3
    4
    5
    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan("com.luv2code.aopdemo")
    public class DemoConfig {
    }
  3. Create main APP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MainDemoApp {
    public static void main(String[] args) {
    // read spring config java class
    AnnotationConfigApplicationContext context =
    new AnnotationConfigApplicationContext(DemoConfig.class);
    // get the bean from the bean factory
    AccountDAO theAccountDAO = context.getBean("accountDAO", AccountDAO.class);
    // call the business method in the component
    theAccountDAO.addAccount();
    // close the spring context
    context.close();
    }
    }
  4. Create an Aspect with @Before

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Aspect
    @Component
    public class MyDemoLoggingAspect {
    // this is where we add all the related advices for logging
    // @Before advice
    @Before("execution(public void addAccount())")
    public void beforeAddAccountAdvice(){
    System.out.println("\n =====>>> Executing @Before advice on addAccount() method!");
    }
    }

    @AfterReturning Advice Type

Successful Execution(No Exception).

Add new method in AccountDAO

Add new Method called findAccounts() in AccountDAO.

Update Main app

1
2
3
4
AccountDAO theAccountDAO = context.getBean("accountDAO", AccountDAO.class);
List<Account> theAccounts = theAccountDAO.findAccounts();
System.out.println("\n\nMain Program: AfterReturningDemoApp:\n---------");
System.out.println(theAccounts);

Add @AfterReturning Advice

1
2
3
4
5
6
7
8
9
10
@AfterReturning(
pointcut = "execution(* com.luv2code.aopdemo.dao.AccountDAO.findAccounts(..))",
returning = "result")
public void afterReturnFindAccountsAdvice(JoinPoint joinPoint, List<Account> result){
// print which method we are advising on
String method = joinPoint.getSignature().toShortString();
System.out.println("\n=======>>> Executing @AfterReturning on method: " + method);
// print out the results of the method call
System.out.println("=======>>> Result is: " +result);
}

Use Case - Post-processing Data

  • Post-processing the data before returning to the caller
  • Format the data or enrich the data.

**Be careful when you modify the data! **

Convert every first name to Upper Case

1
2
3
4
5
6
7
8
9
10
11
12
13
@AfterReturning(
pointcut = "execution(* com.luv2code.aopdemo.dao.AccountDAO.findAccounts(..))",
returning = "result")
public void afterReturnFindAccountsAdvice(JoinPoint joinPoint, List<Account> result){
System.out.println("\n=======>>> The result is: "+result);
convertAccountNamesToUpperCase(result);
System.out.println(("\n======>>> The result is: "+result));
}

private void convertAccountNamesToUpperCase(List<Account> result) {
for(Account account : result)
account.setName(account.getName().toUpperCase());
}

@AfterThrowing Advice Type

Use Case

  • Log the exception
  • Perform auditing on the exception
  • Notify DevOps team via email or SMS
  • Encapsulate this functionality in AOP aspect for easy reuse

Modify Main App

1
2
3
4
5
try{
theAccounts = theAccountDAO.findAccounts(true);
}catch (Exception exc){
System.out.println("\n\n Main Program... Catch Exception:" + exc);
}

Add @AfterThrowing Advice

1
2
3
4
5
6
7
8
@AfterThrowing(
value = "execution(* com.luv2code.aopdemo.dao.AccountDAO.findAccounts(..))",
throwing = "theExc")
public void afterThrowingFindAccountAdvice(JoinPoint joinPoint, Throwable theExc){
String methodName = joinPoint.getSignature().toShortString();
System.out.println("Executing @AfterThrowing advice..." + methodName);
System.out.println("\n======>>> The exception is:"+ theExc);
}

Note: Make sure the parameter name theExc and the throwing value name are matched.

@After Advice Type

@After will run for success or failure(finally).

Sequence Diagram

@After will execute before @AfterThrowing.

1
2
3
4
5
6
@After("execution(* com.luv2code.aopdemo.dao.AccountDAO.findAccounts(..))")
public void afterFinallyFindAccountsAdvice(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().toShortString();
System.out.println("Executing @After Finally advice..."
+ methodName);
}

@Around Advice Type

Use Cases

  • Pre-processing and post-processing data

  • Instrumentation / profiling code

  • How long does it take for a section of code to run?

  • Managing Exception - Swallow / Handle / Stop Exceptions

Proceeding Join Point

Example:

Find out how long does it take to run the getFortune() method?

1
2
3
4
5
6
7
8
9
10
11
12
@Around(value = "execution(* com.luv2code.aopdemo.service.*.getFortune(..))")
public Object afterGetFortune(ProceedingJoinPoint theProceedingJoinPoint) throws Throwable {
String methodName = theProceedingJoinPoint.getSignature().toShortString();
System.out.println("Executing @Around Finally advice..."
+ methodName);
long begin = System.currentTimeMillis();
Object res = theProceedingJoinPoint.proceed(); // Handle to target method, Execute the target method.
long end = System.currentTimeMillis();
long duration = end - begin;
System.out.println("\n======> Duration: " + duration + " milliseconds.");
return res;
}

Resolve Order Issue

Replace System.out.println with logger.info()

private static Logger myLogger = Logger.getLogger(AroundDemoApp.class.getName());

Handling Exception

For an exception thrown from proceeding join point,

  • You can handle / swallow / stop the exception
  • Or you can simply rethrow the exception.

Handle / Swallow / Stop the Exception

  1. @Around advice calls the target method - getFortuneService
  2. There is an exception, go back to the @Around advice.
  3. @Around advice handles the exception and sends back a regular result steam to the calling program.
  4. The result stream makes it back to the AOP proxy and to main application.
  5. The Main App never knows that exception is thrown because it is handle in the @Around advice.

Note: Do not hide the exceptions all the time. Use with caution. If it is a serious problem, just throw an exception to let everyone know.

1
2
3
4
5
6
7
Object res = null;
try {
res = theProceedingJoinPoint.proceed();
} catch (Exception e){
myLogger.warning(e.getMessage());
res = "Major Accident! No worries! Fly you to the work.";
}

Rethrow the Exception

1
2
3
4
catch(Exception e) {
myLogger.warning(e.getMessage());
throw e;
}

PointCut

PointCut Expression

PointCut: a predicate expression for where advice should be applied.

Match Method and Return Types

1
execution(modifier-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
  • Patterns with “?” is optional.
  • Patterns can use wildcards - * - matches everything

Match any addAccount() method that returns void in AccountDAO:

Match any addAccount() method in any class

@Before("execution(public void addAccount())")

Match methods starting with add returning void in any class

@Before("execution(public void add*())")

Match methods starting with add in any class

@Before("execution(* add*())")

Match Method Parameter Types

Match methods starting with add in any class with at least an Account para

@Before("execution(* add*(com.luv2code.aopdemo.Account, ..))")

Match all methods in a given class

PointCut Declarations

  • Easily reuse pointcut expressions
  • Update pointcut in one location
  • Can also share and combine pointcut expressions

Create a pointcut declaration

1
2
@Poinitcut("execution(* add*(com.luv2code.aopdemo.Account, ..))")
private void forDaoPackage() {}

Apply pointcut declaration to advices

1
2
3
4
@Before("forDaoPackage()")
public void method1(){
//...
}

Combine Pointcut Declarations

1
2
@Pointcut("forDaoPackage() && !(setter() || getter())")
private void allMethodsExceptGetterAndSetter(){}

Apply to all method except “getter/setter”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Pointcut("execution(* com.luv2code.aopdemo.dao.*.*(..))")
private void forDaoPackage(){}

@Pointcut("execution(* com.luv2code.aopdemo.dao.*.set*(..))")
private void setter(){}

@Pointcut("execution(* com.luv2code.aopdemo.dao.*.get*(..))")
private void getter(){}

@Pointcut("forDaoPackage() && !(setter() || getter())")
private void allMethodsExceptGetterAndSetter(){}

@Before("allMethodsExceptGetterAndSetter()")
public void beforeAddAccountAdvice(){
System.out.println("\n =====>>> Executing @Before advice on addAccount() method!");
}

Ordering Aspects

Refactor the advices into seperate Aspects and add @Order() annotation.

1
2
3
4
5
6
7
8
9
@Aspect
@Component
@Order(1)
public class MyDemoLoggingAspect {
@Before("com.luv2code.aopdemo.aspect.LuvAopExpressions.allMethodsExceptGetterAndSetter()")
public void beforeAddAccountAdvice(){
System.out.println("\n =====>>> Executing @Before advice on addAccount() method!");
}
}

Note: smaller number represents higher level of execution. -40 > -10 > 100

Read Method Arguments with JoinPoints

1. Access and display Method Signature

1
2
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
System.out.println("Method Signature:"+methodSignature);

2. Access and display Method Arguments

1
2
3
4
Object[] args = joinPoint.getArgs();
for(Object o: args){
// loop through the args
}

Reference: [1]Udemy, Spring & Hibernate for Beginners (including SpringBoot) [2][I want my AOP](https://www.infoworld.com/article/2073918/i-want-my-aop---part-1.html) [3][SpringAOP & Proxy Pattern](https://blog.csdn.net/eson_15/article/details/84933442?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.not_use_machine_learn_pai)