AOP learning notes (Android)

AOP learning notes (Android)


AOP: aspect oriented programming is a technology that realizes the unified maintenance of program functions through dynamic agents during precompiling and running. AOP is the continuation of OOP (object-oriented programming). Using AOP, each part of business logic can be isolated, so as to reduce the coupling between each part of business logic, improve the reusability of programs and improve the development efficiency.

At present, the mainstream AOP implementation methods include AspectJ and JDK dynamic agent

AspectJ

  1. Annotate Aspect classes with Aspect

  2. Specify the enhancement method in the facet class

    The notes used are:

    @ Before (pre enhanced)

    @ After (post enhancement)

    @ Around (surround enhancement)

    @ AfterReturning (return enhancement)

    @ AfterThrowing (exception enhancement)

Using AspectJ on Android requires a plug-in: https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx

dependencies{
	classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'  
}

Current problems of AspectJX

The plug-in currently works with kotlin1 4 there is a compatibility problem, which needs to be in build Gradle adds aspectjx closure and excludes versions/9 to compile normally.

aspectjx {
    exclude 'versions.9'
}

Enhanced annotation

@Before (pre enhancement)

Pre enhancement is performed before the target method is executed.

@Before("execution(* com.skit.androidaop.*.on**(..))")//com. skit. All methods starting with class on under Android App package
fun preOnMethodBefore(jp: JoinPoint) {
  Log.d(TAG, "onClickBefore: $jp ${jp.target} ${jp.target.javaClass.name}")
}

@After (post enhancement)

Post enhancement is performed after the method target is executed.

@After("execution(* com.skit.androidaop.*.on**(..))")
fun preOnMethodAfter(jp:JoinPoint){
  Log.d(TAG, "onClickAfter: $jp ${jp.target}")
}

@After returning

After the target method is executed, the return value of the target method can be obtained in the return enhancement.

@AfterReturning("execution(@com.skit.androidaop.ReturnAfter * *(..))", returning = "i")
fun afterReturing(p: JoinPoint, i: Any) {
  Log.d(TAG, "afterReturing: ${p.signature.name}: $i")
}

@AfterThrowing (exception enhancement)

Exception enhancement occurs after an unhandled exception occurs

@AfterThrowing("execution(* com.skit..*.*(..))", throwing = "e")
fun afterThrowing(jp: JoinPoint, e: Exception) {
  Log.d(TAG, "afterThrowing: ${e.message}")
}

@Surround (surround enhancement)

The original code will be replaced. The notch method using this annotation must have the ProceedingJoinPoint parameter. If you want to execute the original code, you have to call the processed method of the ProceedingJoinPoint object.

@Around("execution(@com.skit.androidaop.SingleClick * *(..))")
fun aroundSingleClick(p: ProceedingJoinPoint) {
  val currentTimeMillis = System.currentTimeMillis()
  if ((currentTimeMillis - time) < 1000) {
    Log.d(TAG, "aroundSingleClick: Click in a second")
  } else {
    p.proceed(p.args)
    time = System.currentTimeMillis()
  }
}

@Pointcut (named pointcut)

@Pointcut is used to define reusable pointcuts

//Top entry point
@Pointcut("execution(@com.skit.androidaop.AClick * *(..))")
fun preWithOnPointcut(){
  
}

//The above entry point is used
@Before("preWithOnPointcut()")
fun before(){
}

@After("preWithOnPointcut()")
fun after(){
}

Tangent function

Before you look at pointcut functions, you need to know wildcards and logical operators

Wildcards are used to match the rules formulated, and logical operators are used for the combination of multiple pointcuts.

wildcard

  1. *Wildcard, which is mainly used to match a single word or a word with a word as a prefix or suffix.
  2. .. Wildcard, which matches multiple elements and must be used with * when representing a class
  3. +Wildcard, which indicates all classes matching the specified class according to type, and must be followed by the class name, such as com skit. bean. Car +, which means to inherit all subclasses of this class, including itself

Logical operator

  1. &&And operator, which is equivalent to the intersection operation of tangent points.
  2. ||Or operator, equivalent to the union operation of tangent points.
  3. ! Non operator, which is equivalent to the inverse set operation of tangent point.
@Before("target(androidx.appcompat.app.AppCompatActivity) && execution(* on*(..))")
fun before(jp: JoinPoint) {
  Log.d(TAG, "before: ${jp.sourceLocation.withinType.name} ${jp.signature.name}")
}

As shown above, it matches Android X appcompat. app. Appcompatactivity class and its subclasses, and the method starts with on

The operation results are shown in the figure below:

call

The difference between call and execution is that when call is the call pointcut, execution is the execution pointcut, that is, call weaves the enhanced code at the call, while execution weaves the enhanced code inside the connection point method.

execution

Execution (access modifier)? Method return type? Full path of the package in which the method is located? Method (method parameter type)? Exception type? (PS: use package full path)

Execution is the most commonly used pointcut function. The facet granularity of execution expression can reach the method level to match methods.

example:

execution(public * *(..)) Public methods that match all classes

execution(* on*(..)) Matches all methods beginning with class on

execution(public java.lang.String *(..)) The access modifier of all matching classes is public and the return type is string

execution(* com.skit.activity.*(..)) Match com skit. All class methods under the activity package (PS: excluding sub packages)

execution(* com.skit.activity..*(..)) Match com skit. All classes and methods under the activity package (PS: including sub packages)

execution(* com.skit..*.*Activity.on*(..)) Match com Methods starting with on of classes ending with activity under the skip package

@annotation (wildcard characters are not supported)

@Annotation is used to mark the target tangent point of an annotation. The function input parameters must be annotations and the package name must be written in full.

@Annotation (COM. Skip. Android. SingleClick) means to enhance any method that annotates the annotation of SingleClick.

within

within represents all methods in a specific domain.

Within (COM. Skip. Activity. *) means com skit. All methods in the activity package.

Within (COM. Skip.. *. * Activity. *) means com All methods in the skip package are all methods of classes ending with Activity.

The functions of within() and execution() are similar. The difference between them is that the minimum range of connection points defined by within() is class level (not interface), while the minimum range of connection points defined by execution() can be accurate to method parameters. Therefore, it can be considered that execution() covers the functions of within().

@within (wildcard characters are not supported)

@within matches the class and its descendants annotated with the specified annotation.

@Within (COM. Skip. AllMethodTime) matches all methods of all classes marked with AllMethodTime and their descendant classes.

target (wildcard characters are not supported)

Target represents the target class. If it is an input parameter type, all methods of the target class match the tangent point.

Target (COM. Skip. Activity. Baseactivity) matches com skit. activity. Baseactivity implements all methods in all classes and their descendants

@target (wildcard characters are not supported)

@Target indicates the target class. If the annotation of the input parameter is marked, all methods of the target class match the tangent point.

@Target (COM. Skip. Allmethodtime) matches all annotations skit. All methods of all classes annotated by allmethodtime.

What is the difference between within and target

within(androidx.appcompat.app.AppCompatActivity) && execution(* on*(..))

	target(androidx.appcompat.app.AppCompatActivity) && execution(* on*(..))

As can be seen from the above figure, within only has the specified class, while target contains the child class and even its parent class

Using AOP method to realize button fast click interception

Create Note

@Target(AnnotationTarget.FUNCTION)//Use in method
@Retention(AnnotationRetention.BINARY)//Retentionpolicy equivalent to Java annotation CLASS
annotation class SingleClick {
}

Create enhancement class

@Aspect
class ClickAspect{
  private var time = 0L
  //Surround enhancement
  @Around("execution(@com.skit.androidaop.SingleClick * *(..))")
  fun aroundSingleClick(p: ProceedingJoinPoint) {
    val currentTimeMillis = System.currentTimeMillis()//Get current time
    if ((currentTimeMillis - time) < 1000) {//Click interval less than 1000 ms
      Log.d(TAG, "aroundSingleClick: Click blocked within one second")
    } else {
      p.proceed(p.args)//Execution method
      time = System.currentTimeMillis()
    }
  }
}

usage

override fun onCreate(savedInstanceState: Bundle?) {
  ...
  binding.singleClick.setOnClickListener {
    singleClick()
  }
  ...
}

@SingleClick
private fun singleClick() {
  Log.d(TAG, "singleClick: ")
}

JDK dynamic agent

JDK dynamic agent mainly implements InvocationHandler interface and injects code in invoke method

First create several classes:

IUser interface

public interface IUser {
    String userName();

    void setName(String name);

    void apply();
}

VipUser class

public class VipUser implements IUser {
    String name;

    @Override
    public String userName() {
        System.out.println("implement userName");
        return name;
    }

    @Override
    public void setName(String name) {
        System.out.println("implement setName");
        this.name = name;
    }

    @Override
    public void apply() {
        System.out.println("implement apply");
    }
}

The AOPHandler class implements the InvocationHandler method

public class AOPHandler implements InvocationHandler {
    VipUser iUser;

    public AOPHandler(VipUser iUser) {
        this.iUser = iUser;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + "Before method execution");
        Object invoke = method.invoke(iUser, args);
        System.out.println(method.getName() + "After method execution\n\n");
        return invoke;
    }

    public IUser apply() {
        Object o = Proxy.newProxyInstance(iUser.getClass().getClassLoader(), new Class[]{IUser.class}, this);
        return (IUser) o;
    }
}

function:

public static void main(String[] args) {
    IUser user = new AOPHandler(new VipUser()).apply();
    user.setName("Shuike");
    System.out.println(user.userName() + "\n");
    user.apply();
}

Proxy. The second parameter of newproxyinstance method can only pass the interface class. Each injection point needs a corresponding interface class. The operation is complicated, and the separation of enhancement code and business code is not achieved. At the same time, JDK dynamic agent is implemented by JDK reflection mechanism, while AspectJ is implemented by precompiling.

After the compiler generates a class file, AspectJ will insert piles into the class file to add code.

Keywords: Java Android kotlin

Added by NevadaSam on Mon, 20 Dec 2021 05:05:15 +0200