AOP Face-Oriented Programming, AspectJ's Application in Android

Keywords: Android Programming Gradle Spring

1. Brief introduction

1.1 Concept of AOP

AOP is the abbreviation of Aspect Oriented Programming, which means: Face-Oriented Programming, a technology to achieve the unified maintenance of program functions through pre-compiled mode and run-time dynamic proxy. AOP is the continuation of OOP, a hotspot in software development, an important content in Spring framework, and a derivative paradigm of functional programming. AOP can isolate all parts of business logic, which reduces the coupling degree between the parts of business logic, improves the reusability of programs, and improves the efficiency of development.

1.2 AOP terminology
  1. Advice: It's the function you want, that's logging, time-consuming computing, etc.
  2. Join Point: It's a lot easier to allow you to notify (Advice) where the connection point can be either before or after each method (both) or when an exception is thrown (spring only supports method join points). AspectJ also allows you to inject constructors or attributes, but it's not usually done, as long as you remember that methods are connected before and after.
  3. Pointcut: Define a pointcut based on the connection points mentioned above. There are 15 methods in one of your classes, so there are more than a dozen join points, right? But you don't want to use notifications in all method attachments (call weaving, let's say next). You just want to call a few of them. What do you do before, after, or when you throw an exception? Define these methods with pointcuts. Let the pointcuts filter the join points and select the methods you want.
  4. Aspect: Aspect is the combination of notification and entry point. Now find out, there is no connection point. The connection point is to make you understand the cut point and understand the concept. Notifications indicate what to do and when to do it (when to know through AOP annotations such as before, after, around), and pointcuts indicate where to do it (specifying exactly which method to use), which is a complete definition of aspect.
  5. weaving is the process of applying facets to target objects to create new proxy objects.
1.3 AOP Annotations and Use
  • @ Aspect: Declare aspects, tag classes
  • @ Pointcut (tangent expression): Define tangent points, tagging methods
  • @ Befor (pointcut expression): Pre-notification, execution before pointcut
  • @ After (tangent expression): After notification, after the tangent
  • @ Around (tangent expression): Around the notification, before and after the tangent
  • @ AfterReturning: Returns a notification and executes after the tangent method returns the result
  • @ AfterThrowing: Exception notification, executed when a tangent throws an exception

@ Pointcut@Befor@Around@After@AfterReturning@AfterThrowing needs to be used in the facet class, i.e. @Aspect class

1.4 AOP Implementation
  1. AspectJ: A seamless extension of JavaTM language for aspect-oriented programming (for Android).
  2. Javassist for Android: A ported version of the Android platform for Javassist, a well-known java class library for bytecode operations.
  3. DexMaker: Java API that generates code at compile time or run time on the Dalvik virtual machine.
  4. ASMDEX: A bytecode operation library similar to ASM, which runs on Android platform and operates Dex bytecode.

This article mainly explains the use of aspectJ in Android.

1.4 Scene

What's the use of AOP? Generally speaking, it is mainly used in scenarios that do not want to intrude into the original code, such as: inserting some log buried points into the business code, performance monitoring, dynamic permission control and even code debugging, which will lead to unclear business code logic, business separation and other issues.

2. AspectJ use

2.1 How AspectJ integrates
dependencies {   
  classpath 'com.android.tools.build:gradle:3.4.2'
//Reference to dependencies of aspcetJ
  classpath 'org.aspectj:aspectjtools:1.8.9'
  classpath 'org.aspectj:aspectjweaver:1.8.9'
    }

Then add the corresponding dependencies in the build.gradle of the specific module used by the project:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    //Reference to jar of aspcetJ
    implementation 'org.aspectj:aspectjrt:1.8.9'
}

Then add the parse code block to the module's build.gradle root path:

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger

android.applicationVariants.all{ variant ->

    JavaCompile javaCompile = variant.javaCompiler
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true)
        new Main().run(args, handler)
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break
            }
        }
    }
}

Re rebuild and aspectJ is integrated into our project.

2.2 Configuration of related tangent classes

The code is as follows:

@Aspect
public class LogTraceAspect {

    private static final String TAG = "LOG";

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore:" + key);
    }
}

The above is a simple tangent class, the specific annotations can be seen in 1.3 description, this is very simple, the difficulty is the tangent expression. Understanding pointcut expressions makes AOP easy.

2.3 Tangent Point Expressions

The composition of the tangent expression is as follows:

Execution (< modifier mode > < return type mode > < method name mode > (< parametric mode >) < exception mode >?)

In 2.2, there is an expression in the tangent class.

"execution(* android.app.Activity.on*(...))"
Except for return type mode, method name mode and parameter mode, the others are optional.

The expression of 2.2 illustrates that execution generally specifies the execution of a method because the return type is unrestricted. Here * is used instead, and Android.app.Activity.on * represents the full path of the function, meaning all methods starting with on under all package names Android.app.activity. Later (...) The parameter representing the method is any value.

Classification and method description of JPoint

JPoint Explain Pointcut grammatical description
method execution Execution of general specified methods execution(MethodSignnature)
method call Functional methods are called call(MethodSignnature)
constructor call The constructor is called call(ConstructorSignature)
constructor exxcution Internal execution of constructor execution(ConstructorSignature)
field get Read variables get(FieldSignature)
field set Write variables set(FieldSignature)
handler exception handling Note: handler(TypeSignature) can only be used in conjunction with @Before(), does not support @After and @Around, etc.

Examples

    @Before("call(* android.app.Activity.on*(..))")
    public void callMethod(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "callMethodBefore" + key);
    }

This code means matching all activities and printing the log before the on method call of activity.

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore:" + key);
    }

This code means to match all activities and print the log before the method body in the activity on method executes.

The difference between execution and call is that the call execution location is before the method, and the execution execution location is the first line inside the method.

Description of get and set methods

First, we define an object class.

public class TestUser {

    private int  age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Method of implementation:

 /**
     * Get the construction method of the class and assign value to the method
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("get(com.android.aspectj.TextUser.age)")
    public String onGetAge(ProceedingJoinPoint joinPoint) throws Throwable{
        // Execute the original code
        Object obj = joinPoint.proceed();
        String age = obj.toString();
        Log.e(TAG, "age: " + age);
        return "18";
    }

Gets the method that we set the age for the testUser class, and returns the age set to 18 to the setAge () method. The get method is the same.

2.4 Annotation Use

Sometimes we don't know the name of a specific third party, or many methods need to be recorded by us, so annotations can be a good solution to this requirement.
First come from the definition of a commentary:

@Retention(RetentionPolicy.CLASS)
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
    public @interface Debug {
    }

Then, in the Aspect file, reference the listening annotations:

 @Before("execution(@com.android.aspectj.log.LogTraceAspectInterface.Debug * * (..))")
    public void onActivityDebug(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "Debug:" + key);
    }

Note that the path must be the full path of our custom annotations
Finally, you can simply introduce annotations where you want to record them.

 
    @LogTraceAspectInterface.Debug
    public void click(){
        Toast.makeText(getApplicationContext(),"Ha-ha",Toast.LENGTH_SHORT).show();
    }

Well, that's all for this brief introduction to Aspect. I've uploaded the code to github
Welcome to join me V 18201726027 or chat and discuss, learn together, grow up together!
Next Preview: AspectJ develops the practical application of slice-oriented programming sub-module.

Posted by dsartain on Tue, 20 Aug 2019 00:00:03 -0700