AOP aspect oriented programming: the use of Aspect J

For AOP aspect oriented programming, there are many types of AOP framework at present. For details, please refer to the article: Talk about Android AOP technical scheme

Here we will explain the AOP framework: the use of AspectJ.

1. Section oriented programming

1.1 programming paradigm

In the current programming language, the programming paradigm can be divided into the following types

  • Process oriented programming: such as C language

  • Object oriented programming: such as Java, C++

  • Functional programming

  • Event driven programming

  • Aspect oriented programming

1.2 what is AOP

AOP is called Aspect Oriented Programming. It is a technology that realizes the unified maintenance of program functions through precompiled mode and dynamic agent during operation.

Since AOP is called aspect oriented programming, what is the aspect?

Aspect: in aspect oriented programming, we still define common functions in one place, but we can define how and where this function should be applied by declaration without modifying the affected classes. Crosscutting concerns can be modularized into special classes called facets.

The aspect definition described above may still be too abstract. A simple understanding is that business code is added to the method. The method to which the business code is added does not need to be modified. Instead, the business code can be cut into that method by defining the method implementation in an aspect class.

For AOP aspect oriented programming, we need to pay attention to several points:

  • It is a programming paradigm, not a programming language

  • It solves specific problems and cannot solve all problems

  • It is a supplement to OOP object-oriented programming, not a substitute for competition

1.3 original intention of AOP

Since AOP aspect oriented programming is proposed, and AOP is a supplement to OOP, it is mainly to solve some phenomena and problems that OOP cannot solve.

The original intention of AOP is to solve the following problems:

  • DRY (Don't Repeat Yourself): solve the problem of code duplication

  • Soc (Separation Of Concerns): solve the Separation Of Concerns. Separation can be divided into three types:

    • Horizontal separation: for example, the presentation layer - > service layer - > persistence layer in MVC

    • Vertical separation: for the separation from the perspective of business requirements, the module is divided

    • Section separation: for the separation from the perspective of functionality, separate functional requirements from non functional requirements

1.4 benefits and application scenarios of using AOP

In practice, AOP aspect oriented programming can bring the following benefits:

  • Focus on one concern / crosscutting logic

  • You can easily add / delete concerns

  • Less intrusiveness, enhanced code readability and maintainability

As a supplement to OOP, AOP has its own suitable use scenarios, such as permission control, cache control, transaction control, audit log, performance monitoring, exception handling and so on.

2 use of AOP

2.1 AspectJ access

There are two ways to access AspectJ in Android:

  • Refer to Jake Wharton's Hugo open source framework

  • Access the hujiang AspectJ plug-in

Hugo framework is also the actual use of AspectJ in Android. For this way of access, please refer to the following article: AOP buried point from entry to abandonment (I) , and the github address of Hugo framework: Hugo.

The second plug-in accesses the reference github address: ABCD AspectJ plug in . It should be noted that if the code of AspectJ aspect is written in another module, and if you want to cut into the code under other modules, the module to be cut in needs to introduce a plug-in.

2.2 AspectJ

AspectJ needs to be used in the AOP of Java language. It is an aspect oriented framework that extends the Java language. AspectJ defines AOP syntax. It has a special compiler to generate Class files that comply with Java byte coding specifications.

First, use a simple example to illustrate a simple use:

@Target(value = {ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Arithmetic {
}

@Aspect
public class AopHelper {
	
	// Tangent point 1
	@Pointcut("@annotation(Arithmetic)")
	public void pointcut1() {
	}

	// Tangent point 2
	@Pointcut("execution(* com.example.demo.MainActivity.onCreate(..))")
	public void pointcut2() {
	}

	// Execute the @ Arithmetic annotated method before executing it
	@Before("pointcut1()")
	public void execBeforeAnnotatedArithmeticMethod() {
		Log.i("AOP", "execute before annotated @Arithmetic method");
	}
}

public class MainActivity extends AppCompatActivity {
	
	@Arithmetic
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		Log.i("AOP", "execute method onCreate");
	}
}

Run the above program to print out the log of @ Arithmetic annotated methods and classes. By adding pointcuts to the facet class, you can cut in when executing the specified code. There is basically no need to add any code, which is very convenient.

Next, I will explain the use of AspectJ in detail.

2.3 section expression

Expression section expression consists of three parts:

  • Wildcards: there are some methods and classes that need to be listed one by one. If it's too troublesome, you can use wildcards. There are three kinds of wildcards used in tangent expression:
wildcard describe
* Match any number of characters
+ Matches the specified class and its subclasses
.. Generally used to match any number of subpackages or parameters
  • Operators: specify the combination of execution conditions that meet the requirements. There are three main operators used in tangent expression:
Operator describe
&& And operators, which can be executed when the conditions are met
|| Or operator, which can be executed as long as one condition is met
! Non operator, condition not satisfied
  • designators: describes how to specify how to match the Java class or method to be cut in.

    Next, let's talk about the match of indicators in the tangent expression.

2.3.1 within expression

within is a method that matches the types of all methods and classes under the specified package. It is mostly used with other indicators.

Arithmetic.java

package com.example.demo;

public interface Arithmetic {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

ArithmeticCalculator.java

package com.example.demo;

import android.util.Log;

public class ArithmeticCalculator implements Arithmetic {
    private static final String TAG = "AOP";
    
    @Override
    public int add(int i, int j) {
        Log.i(TAG, "call method add(i, j), i = " + i + ", j = " + j);
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        Log.i(TAG, "call method sub(i, j), i = " + i + ", j = " + j);
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        Log.i(TAG, "call method mul(i, j). i = " + i + ", j = " + j);
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        Log.i(TAG, "call method div(i, j), i = " + i + ", j = " + j);
        return i / j;
    }
}

AspectWithin.java

@Aspect
public class AspectWithin {
    private static final String TAG = "AOP";

    // Match all the methods in the arithmeticalcalculator class
    // If the indicator matches an interface, the actual code executes the implementation class of the interface, and the pointcut will not match the execution
    // It should be noted that if there is a method call in the method of the pointcut, it will also be cut in
    @Pointcut("within(com.example.demo.ArithmeticCalculator)")
    public void matchType() {
    }

    // Match com example. Methods of all classes under demo package and sub package
    // If the AOP aspect class also throws NoAspectBoundException under the specified package, you need to exclude this aspect class
    // within(com.example.demo.*) && !within(com.example.aop.AspectWithin)
    @Pointcut("within(com.example.demo.*)")
    public void matchPackage() {
    }

    @Before("matchType()")
    public void withinMatchType() {
        Log.i(TAG, "withinMatchType");
    }

    @Before("matchPackage()")
    public void withinMatchPackage() {
        Log.i(TAG, "withinMatchPackage");
    }
}

// Call the matching method
ArithmeticCalculator calculator = new ArithmeticCalculator();
calculator.add(10, 5);

2.3.2 object matching

There are three methods for object matching: this(), bean(), target(). Specify the matching object type and all methods under the object abstract type (including interface).

package com.example.aop;

import android.util.Log;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectObject {
    private static final String TAG = "AOP";

    // this and target can be considered the same without introduction
    // introduction can dynamically add methods to a class
    // When there is an introduction, this can cut into these methods generated by the introduction, but the target cannot

    // The target object matching the AOP object is the method of the specified type, that is, the method of the AOP proxy object of arithmeticalcalculator
    // AOP generates proxy objects, and this refers to proxy objects
    // Without introduction, it is the same as target, so this(com.example.demo.Arithmetic) can also be matched
    @Pointcut("this(com.example.demo.ArithmeticCalculator)")
    public void matchThis() {
    }

    // The method of matching the target object that implements the Arithmetic interface (not the object after AOP proxy), here is the method of Arithmetic calculator
    // target refers to the original object
    // It is the same as this without introduction
    @Pointcut("target(com.example.demo.Arithmetic)")
    public void matchTarget() {
    }

    @Before("matchThis()")
    public void thisMatch() {
        Log.i(TAG, "thisMatch");
    }

    @Before("matchTarget()")
    public void targetMatch() {
        Log.i(TAG, "targetMatch");
    }
}

// Call the matching method
// Both arithmeticalcalculator and arithmeticalcalculator2 implement the arithmetical interface
ArithmeticCalculator calculator = new ArithmeticCalculator();
calculator.add(10, 5);

ArithmeticCalculator2 calculator2 = new ArithmeticCalculator2();
calculator2.add(5, 10);

2.3.3 parameter matching

The args parameter matches the method that specifies one or more corresponding types.

Arithmetic.java

package com.example.demo;

public interface Arithmetic {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
    void test1(Long i);
    void test2(long i, String s);
}

ArithmeticCalculator.java

package com.example.demo;

import android.util.Log;

public class ArithmeticCalculator implements Arithmetic {
    private static final String TAG = "AOP";

    @Override
    public int add(int i, int j) {
        Log.i(TAG, "call method add(i, j), i = " + i + ", j = " + j);
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        Log.i(TAG, "call method sub(i, j), i = " + i + ", j = " + j);
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        Log.i(TAG, "call method mul(i, j). i = " + i + ", j = " + j);
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        Log.i(TAG, "call method div(i, j), i = " + i + ", j = " + j);
        return i / j;
    }

    @Override
    public void test1(Long i) {
        Log.i(TAG, "call method test1(i), i = " + i);
    }

    @Override
    public void test2(long i, String s) {
        Log.i(TAG, "call method test2(i), i = " + i + ", s = " + s);
    }
}

AspectArgs.java

package com.example.aop;

import android.util.Log;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectArgs {
    private static final String TAG = "AOP";

    // Matches any method that has only one parameter of type Long
    @Pointcut("within(com.example.demo.*) && args(Long)")
    public void matchArgs1() {
    }

    // Matches a method whose first argument is of type Long
    @Pointcut("within(com.example.demo.*) && args(Long,..)")
    public void matchArgs2() {
    }

    @Pointcut("within(com.example.demo.*) && args(Long, String)")
    public void matchArgs3() {
    }

    @Before("matchArgs1()")
    public void args1Match() {
        Log.i(TAG, "arg1Match");
    }

    @Before("matchArgs2()")
    public void args2Match() {
        Log.i(TAG, "args2Match");
    }

    @Before("matchArgs3()")
    public void args3Match() {
        Log.i(TAG, "args3Match");
    }
}

// Call the matching method
 ArithmeticCalculator calculator = new ArithmeticCalculator();
 calculator.test1(10L);
 calculator.test2(10L, "test");

2.3.4 annotation matching

There are four types of annotation matching: @ annotation(), @ within, @ target, @ args.

ArithmeticAnnotation.java

package com.example.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ArithmeticAnnotation {
}

ArithmeticAnnotationClass.java

package com.example.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS) // Specify the CLASS level or RUNTIME
@Target(ElementType.TYPE)
@Inherited
public @interface ArithmeticAnnotationClass {
}

ArithmeticAnnotationRuntime.java

package com.example.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // Specifies the RUNTIME level
@Target(ElementType.TYPE)
@Inherited
public @interface ArithmeticAnnotationRuntime {
}

AspectAnotation.java

package com.example.aop;

import android.util.Log;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectAnnotation {
    private static final String TAG = "AOP";

    // Match the class, method or member variable annotated with @ arithmeticaannotation annotation
    @Pointcut("@annotation(com.example.aop.ArithmeticAnnotation)")
    public void annotationMatch() {
    }

    // Match all methods under the class annotated with @ ArithmeticAnnotationClass
    // The RetentionPolicy level of annotation is required to be CLASS or above, that is, CLASS and RUNTIME will be executed, and the SOURCE level will not be executed
    // The methods of annotated classes and their subclasses will be intercepted
    @Pointcut("@within(com.example.aop.ArithmeticAnnotationClass)")
    public void annotationMatchWithin() {
    }

    // Match all methods under the class annotated with @ ArithmeticAnnotationRuntime annotation
    // The RetentionPolicy level of annotation is required to be RUNTIME, that is, neither SOURCE nor CLASS will be executed
    // The methods of annotated classes and their subclasses will be intercepted
    @Pointcut("@target(com.example.aop.ArithmeticAnnotationRuntime)")
    public void annotationMatchTarget() {
    }

    // Match the method marked with @ arithmeticaannotation annotation on the passed in parameter class
    @Pointcut("@args(com.example.aop.ArithmeticAnnotation) && within(com.example.demo..*)")
    public void annotationMatchArgs() {
    }

    @Before("annotationMatch()")
    public void matchAnnotation() {
        Log.i(TAG, "matchAnnotation");
    }

    @Before("annotationMatchWithin()")
    public void matchAnnotationWithin() {
        Log.i(TAG, "matchAnnotationWithin");
    }

    @Before("annotationMatchTarget()")
    public void matchAnnotationTarget() {
        Log.i(TAG, "matchAnnotationTarget");
    }

    @Before("annotationMatchArgs()")
    public void matchAnnotationArgs() {
        Log.i(TAG, "matchAnnotationArgs");
    }
}

ArithmeticCalculator.java

package com.example.demo;

import android.util.Log;

import com.example.aop.ArithmeticAnnotation;
import com.example.aop.ArithmeticAnnotationClass;
import com.example.aop.ArithmeticAnnotationRuntime;

@ArithmeticAnnotation // @Annotation and @ args annotation match
@ArithmeticAnnotationClass // @within annotation matching
@ArithmeticAnnotationRuntime // @target annotation matching
public class ArithmeticCalculator implements Arithmetic {
    private static final String TAG = "AOP";

    @Override
    public int add(int i, int j) {
        Log.i(TAG, "call method add(i, j), i = " + i + ", j = " + j);
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        Log.i(TAG, "call method sub(i, j), i = " + i + ", j = " + j);
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        Log.i(TAG, "call method mul(i, j). i = " + i + ", j = " + j);
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        Log.i(TAG, "call method div(i, j), i = " + i + ", j = " + j);
        return i / j;
    }

    @Override
    public void test1(Long i) {
        Log.i(TAG, "call method test1(i), i = " + i);
    }

    @Override
    public void test2(long i, String s) {
        Log.i(TAG, "call method test2(i), i = " + i + ", s = " + s);
    }
}

// Call matching method
ArithmeticCalculator calculator = new ArithmeticCalculator();
calculator.add(10, 5);

2.3.5 execution expression

Format:

// tagging? Yes, it's optional, isn't it? It must be declared
execution(
	modifier-pattern? // Method modifier expression
	ret-type-pattern // Return type expression
	declaring-type-pattern? // Package name object type expression
	name-pattern(param-pattern) // Method name expression (parameter expression)
	throws-pattern? // Throw exception expression
)

Also use an example to explain execution.

package com.example.demo.demo2; // Create a class under the sub package of the demo to verify and intercept the sub package

import android.util.Log;

public class Test {
    private static final String TAG = "AOP";

    public void test() {
        Log.i(TAG, "test");
    }
}

package com.example.aop;

import android.util.Log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectExecution {
    private static final String TAG = "AOP";

    // execution(
    //      public / / method modifier
    //      */ / any return value
    //      */ / any class name
    //      *(..)   // * Is any method name, (..) It is any parameter [if you want to match the parameter without parameter, it is ()]
    // )
    // This pointcut expression can only intercept the corresponding package. If you also want to intercept the methods under the corresponding package sub package, you only need to use it under the package name Means, i.e. com example. demo..
    // execution(public * com.example.demo..*.*(..))
    @Pointcut("execution(public * com.example.demo.*.*(..))")
    public void matchExecution() {
    }

    // execution(
    //      public / / method modifier
    //      Int / / return int type
    //      com.example.demo.Arithmetic*.   //  Class name prefixed with arithmetic
    //      *(int,int) / / * refers to any method name, (int,int) refers to the passed parameter, and all of them are of type int
    // )
    @Pointcut("execution(public int com.example.demo.Arithmetic*.*(int, int))")
    public void matchExecution2() {
    }

    // execution(
    //      public / / method modifier
    //      */ / return int type
    //      */ / any class name
    //      *(..)                                       // * Is any method name, (int,int) is the passed parameter, all of which are of type int
    //      throws java.lang.IllegalArgumentException / / matches the exception thrown
    // )
    @Pointcut("execution(public * com.example.demo.*.*(..) throws java.lang.IllegalArgumentException)")
    public void matchExecution3() {
    }

    @Before("matchExecution()")
    public void executionMatch() {
        Log.i(TAG, "executionMatch");
    }

    @Before("matchExecution2()")
    public void execution2Match() {
        Log.i(TAG, "execution2Match");
    }

    @Before("matchExecution3()")
    public void execution3Match(JoinPoint joinPoint) {
        Object clazz = joinPoint.getThis();
        Log.i(TAG, "execution3Match, clazz = " + clazz);
    }
}

// Call matching method
ArithmeticCalculator calculator = new ArithmeticCalculator();
calculator.add(10, 5);
// Demo sub package: the class under demo2 package. Verify that it matches com example. demo..
Test test = new Test();
test.test();

2.3.6 advice notes

advice annotations can also be considered as notifications in AOP. There are five types of notifications:

  • Pre notification: call the notification function before the target method is called

  • Post notification: the notification is called after the target method is completed. At this time, the output of the method is not concerned

  • Return notification: call notification after the target method is successfully executed

  • Exception notification: call notification after the target method throws an exception

  • Surround notification: the notification wraps the notified method and performs custom behavior before and after the notified method is called

The five notices have corresponding notes:

annotation type
@Pointcut Define a pointcut to declare a reusable pointcut expression
@Before Pre notification, executed before the start of the target method
@After Post notification, executed after the target method is executed (whether or not an exception occurs)
@AfterReturning Returns a notification, which is executed after the method returns
@AfterThrowing Exception notification, you can specify that the notification will be executed when a specific exception occurs
@Around The surround notification needs to carry the type parameter of ProceedingJointPint. The surround notification must have a return value, which is the return value of the target method
AopHelper.java

@Aspect
public class AopHelper {
    private static final String TAG = "AOP";

    // Declare reusable pointcut expressions; Generally, this method does not need to add other code
    @Pointcut("execution(* com.example.demo.ArithmeticCalculator.*(..))")
    public void declaredPointcutExpression() {
    }

    // Declare a pre notification: execute before the target method starts
    // JoinPoint can obtain parameters as parameters, such as method name and method parameters
    @Before("declaredPointcutExpression()")
    public void pointcutBeforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        Log.i(TAG, "pointcut before, methodName = " + methodName + ", args = " + args);
    }

    // Post notification: executed after the target method is executed (whether an exception occurs or not)
    // The result of the target method execution cannot be accessed in the post notification
    @After("declaredPointcutExpression()")
    public void pointcutAfterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Log.i(TAG, "pointcut after, methodName = " + methodName);
    }

    // Return notification: execute after the method returns
    // You can access the return value of the method. The specified returning is the return value of the method
    @AfterReturning(value = "declaredPointcutExpression()", returning = "result")
    public void pointcutAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        Log.i(TAG, "pointcut after returning, methodName = " + methodName + ", result = " + result);
    }

    // Exception notification, you can specify that the notification will be executed when a specific exception occurs
    // If the Exception is changed to NullPointException and the Exception thrown is ArithmeticException, the Exception notification will not be executed
    @AfterThrowing(value = "execution(* com.example.demo.ArithmeticCalculator.div(..))", throwing = "ex")
    public void pointcutAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        Log.i(TAG, "pointcut after throwing, methodName = " + methodName + ", ex = " + ex);
    }

    @Around("declaredPointcutExpression()")
    public Object pointcutAroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
        Object result;
        String methodName = proceedingJoinPoint.getSignature().getName();

        try {
            // Before advice 
            Log.i(TAG, "pointcut around before, methodName = " + methodName);
            // Execution target method
            result = proceedingJoinPoint.proceed();
            // Return notification
            Log.i(TAG, "pointcut around, methodName = " + methodName);
        } catch (Throwable throwable) {
            // Exception notification
            Log.i(TAG, "pointcut around exception, ex = " + throwable);
            throw new RuntimeException(throwable);
        }

        // Post notification
        Log.i(TAG, "pointcut around after, methodName = " + methodName);

        return result;
    }
}

ArithmeticCalculator calculator = new ArithmeticCalculator();
calculator.add(10, 5);
try {
    calculator.div(5, 0);
} catch (Exception e) {
    e.printStackTrace();
}

Output:
// Pre, post, return, exception notification
pointcut before, methodName = add, args = [10, 5]
call method add(i, j), i = 10, j = 5
pointcut after, methodName = add
pointcut after returning, methodName = add, result = 15
pointcut before, methodName = div, args = [5, 0]
call method div(i, j), i = 5, j = 0
pointcut after, methodName = div
pointcut after throwing, methodName = div, ex = java.lang.ArithmeticException: divide by zero

// Around Advice 
pointcut around before, methodName = add
call method add(i, j), i = 10, j = 5
pointcut around, methodName = add
pointcut around after, methodName = add
pointcut around before, methodName = div
call method div(i, j), i = 5, j = 0
pointcut around exception, ex = java.lang.ArithmeticException: divide by zero

Tags: Optimize

Posted by jordz on Sun, 15 May 2022 06:25:06 +0300