AOP is a platitude topic, full name “Aspect Oriented Programming”, stands for Aspect Oriented Programming. Due to the object-oriented programming thought of promoting high cohesion and low coupling architectural style, the visibility of the variation between modules code, which makes the implement the following requirements become very complex: statistics buried point, log output, permissions, interception, etc., if the manual coding, code of invasive is too high and not conducive to expand, AOP technology arises at the historic moment.

AOP

The aspect in AOP is more visual, the business modules are like tiled in a container. If we need to add a click event burying point to each module now, AOP is like inserting a virtual aspect between all the business modules. When all the subsequent click events pass through this aspect, we have the opportunity to do something extra.

Virtual because the process is non-invasive to the specific business scenario, the business code does not change, and the new code logic does not require additional adaptation. This process is a bit like OkHttp’s interceptor, or rather a concrete implementation of aspect.

This article is an introduction to AspectJ, a tool that makes it easy to implement simple AOP requirements without needing to understand relatively complex underlying technologies such as compilation principles and bytecode structures.

On Android, an AspectJX plug-in from Hujiang is commonly used. It works as follows: The Gradle Transform iterates through and matches all pointcuts declared in the AspectJ file between the class file generation and the dex file generation, then weaves the pre-declared code around the pointcuts.

As can be seen from the description, the whole process takes place at compile time, which is a static weaving mode. Therefore, the compilation time will be increased to a certain extent, but the runtime efficiency of the program will hardly be affected.


This paper is roughly divided into three parts.

  1. Syntax and usage of AspectJ.
  2. Play AspectJ with Jake Wharton’s open source project Hugo.
  3. Problems facing AspectJ.

What can AspectJ do?

In general, AOP serves relatively basic and fixed requirements. Common scenarios in practice include:

  • Statistics buried point
  • Log printing/logging
  • Data validation
  • Behavior to intercept
  • Performance monitoring
  • Dynamic permission control

If you have such a requirement in your project (and you almost certainly do), consider implementing it through AspectJ.

In addition to weaving in code, AspectJ can also add implementation interfaces and member variables to classes. This is not the focus of this article, but you can learn more about it after you learn the basics.

Environment configuration

On The Android platform, we typically use the Aspectjx plug-in mentioned above to configure the AspectJ environment through AspectJ annotations.

  1. You rely on AspectJX in your project root directory, build.gradle
dependencies {
    classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: 2.0.4'
}
Copy the code
  1. Declare the plug-in in the build.gradle file of the module that needs to support AspectJ.
apply plugin: 'android-aspectjx'
Copy the code

At compile time AspectJ iterates through all class files in the project (including classes from third-party libraries) looking for a qualifying pointcut. To speed up the process or narrow the scope of code weaving, we can exclude classes that specify package names by using exclude.

# app/build.gradleAspectjx {// exclude all class files and libraries (jar files) containing 'android.support' from the package path'android.support'
}
Copy the code

In debug we focus more on compilation speed and can turn off code weaving.

# app/build.gradleAspectjx {// Disables the AspectJx function enabledfalse
}
Copy the code

If AspectJ is turned off, all classes in the project will not be able to access APK, and various ClassNotFoundException will occur. There has been an Issue but not yet resolved. I tried to roll back the version to version 2.0.0 and found no such problem. If you currently have a need for dynamic shutdown, it is recommended not to use the latest version.

The basic grammar

Once the environment is configured, we need to write the aspect code using AspectJ annotations.

  • @aspect uses it to declare a class representing an Aspect that needs to be executed.
  • Pointcut declares a Pointcut.
  • @Before/@After/@Around/… (collectively referred to as Advice types) declare that the aspect code is executed before, after, and during the pointcuts.

This may sound confusing to you, but let’s put it another way.

If you are the designer of an AOP framework, the first thing you need to understand is its basic components. If you’re going to do code weaving, do you have to configure the code weaving point? This point is called a Pointcut, and with this point we also need to specify the code that we want to weave in. Where does that code go? It’s written inside a method annotated @before / @after / @around. With the weave point and weave code, you also need to tell the framework that it is a section-oriented configuration file, which requires the @aspect declaration on the class.

For a simple example, see Github Sample_AspectJ.

Public class Pointcut {@pointcut ();"call(* com.wandering.sample.aspectj.Animal.fly(..) )") / / (2) public voidcallMethod() {
    }

    @Before("callMethod()")//③
    public void beforeMethodCall(JoinPoint joinPoint) {
        Log.e(TAG, "before->"+ joinPoint.getTarget().toString()); / / (4)}}Copy the code

We have a fly method in our pre-prepared Animal class.

public class Animal {
    public void fly() {
        Log.e(TAG, "animal fly method:" + this.toString() + "#fly"); }}Copy the code

This class is declared to be an AspectJ configuration file.

(2) specifies a code in weave, annotation of call (* com. Wandering. Sample. Aspectj. Animal. The fly (..) ) is a pointcut expression, the first * indicates that the return value can be of any type, followed by the package name + class name + method name, the parentheses indicate the argument list,.. This expression matches any argument of any type and specifies a time when the fly method of the Animal class is called.

(3) declare the Advice type as Before and specify the pointcut as indicated by callMethod above.

④ is the actual woven code.

Insert the code ④ before the fly method of the Animal class is called.

Write the test code and call the fly method. Run the observe log output and you will find that the before-> log is printed before the Animal fly log. For details, see sample project MethodAspect.

Let’s decompile APK again and see what we weave in.

The selection in the red box is the code that AspectJ has woven in for us.

We’ve seen the basic usage of AspectJ from the examples above, but the syntax of AspectJ can be quite complex. Let’s look at the syntax.

Join Point

The above example misses the concept of join points, which represent points that can be woven into code and are part of the Pointcut. Due to the extensive syntax content, we can refer to the grammar manual in practical use. We list some of the Join points:

Joint Point meaning
Method call Method called
Method execution Methods to perform
Constructor call The constructor is called
Constructor execution Constructor execution
Static initialization Static block initialization
Field get Reads the properties
Field set Write attributes
Handler Exception handling

The difference between Method call and Method execution is often compared. Take Animal’s fly Method as an example. The demo code is as follows:

Animal a = Animal();
a.fly();
Copy the code

If we declare the weaving point to be call and assume that the Advice type is before, the structure of the weaving code looks like this.

Animal a = new Animal(); / /... I'm weaving in code a.fly();Copy the code

If the weaving point we declare is execution, the structure of the code after weaving looks like this.

public class Animal {
    public void fly() {/ /... I'm just weaving in log.e (TAG,"animal fly method:" + this.toString() + "#fly"); }}Copy the code

The essential difference is that the invocation object is different. Call is woven into the location where the specified method is called, while execution is woven inside the specified method.

Pointcut

Pointcuts are specific Pointcuts and basically Pointcuts correspond to Join points.

Joint Point Pointcuts expression
Method call call(MethodPattern)
Method execution execution(MethodPattern)
Constructor call call(ConstructorPattern)
Constructor execution execution(ConstructorPattern)
Static initialization staticinitialization(TypePattern)
Field get get(FieldPattern)
Field set set(FieldPattern)
Handler handler(TypePattern)

Pointcuts has other selection methods in addition to the above selection corresponding to Join Point.

Pointcuts expression instructions
within(TypePattern) Join points in TypePattern compliant code
withincode(MethodPattern) Join Point in some methods
withincode(ConstructorPattern) Join points in some constructors
cflow(Pointcut) Pointcut selects all Join points in the control flow of Pointcut P, including P itself
cflowbelow(Pointcut) Pointcut selects all Join points in the control flow of Pointcut P, excluding P itself
this(Type or Id) Whether the this object to which the Join Point belongs is instanceOf Type or the Type of Id
target(Type or Id) Whether the object on which the Join Point is located (such as the object to which the call or execution operator applies) is of the Type instanceOf Type or Id
args(Type or Id, …) The type of a method or constructor parameter
if(BooleanExpression) ThisJoinPoint can only use static attributes, Pointcuts or Advice exposed arguments, and thisJoinPoint objects

this vs. target

This is a confusing point with target.

# MethodAspect.java
public class MethodAspect {
    @Pointcut("call(* com.wandering.sample.aspectj.Animal.fly(..) )")
    public void callMethod() {
        Log.e(TAG, "callMethod->");
    }

    @Before("callMethod()")
    public void beforeMethodCall(JoinPoint joinPoint) {
        Log.e(TAG, "getTarget->" + joinPoint.getTarget());
        Log.e(TAG, "getThis->"+ joinPoint.getThis()); }}Copy the code

Fly caller:

# MainActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Animal animal = new Animal();
    animal.fly();
}
Copy the code

The running results are as follows:

getTarget->com.wandering.sample.aspectj.Animal@509ddfd
getThis->com.wandering.sample.aspectj.MainActivity@98c38bf
Copy the code

That is, target refers to the owner of the pointcut method, and this refers to the instance object that is woven into the class to which the code belongs.

Let’s make a slight change to “call” at the pointcut.

The result looks like this:

getTarget->com.wandering.sample.aspectj.Animal@509ddfd
getThis->com.wandering.sample.aspectj.Animal@509ddfd
Copy the code

According to the above analysis, it is also consistent with this result.

Conditions of operation

Pointcut expressions can also use conditional identifiers, such as! , &&, | |.

Take Hugo:

# Hugo.java
@Pointcut("within(@hugo.weaving.DebugLog *)")
public void withinAnnotatedClass() {}

@Pointcut("execution(! synthetic * *(..) ) && withinAnnotatedClass()")
public void methodInsideAnnotatedType() {}
Copy the code

The first pointcut specifies any classes and methods that contain DebugLog annotations, and the second pointcut specifies any methods that execute non-inner classes within the first pointcut range. Combined, the statement is an arbitrary declaration of the DebugLog annotation method.

Weaving.DebugLog * and! synthetic * *(..) Correspond to the TypePattern and MethodPattern mentioned in the table above.

Next, we need to understand the specific syntax of these patterns, through which we can write expressions that meet our own needs.

The Pattern type grammar
MethodPattern [!] [@annotation] [public,protected,private] [static] [final] Return value type [class name.] Method name (parameter type list) [throws exception type]
ConstructorPattern [!] [@annotation] [public,protected,private] [final] [Class name.]new(parameter type list) [throws exception type]
FieldPattern [!] [@annotation] [public,protected,private] [static] [final] Property type [class name.] Property name
TypePattern The same applies to other types of Pattern, using ‘! ‘, ‘, ‘… ‘, ‘+’, ‘! ‘matches all strings except. ‘*’ uses things alone to match any type, ‘.. ‘Matches any string, ‘.. ‘used alone to match any type of any length, ‘+’ to match itself and its subclasses, and ‘… ‘is an indefinite number

See Pointcuts for more useful syntax.

Let’s look at a few more examples:

execution(void setUserVisibleHint(..) ) && target (android. Support. The v4. App. Fragments) && args (Boolean) – perform fragments and its subclasses of setUserVisibleHint (Boolean) method.

execution(void Foo.foo(..) ) && cflowbelow(execution(void Foo.foo(..) Foo. Foo (); Foo. Foo (); Foo. Foo (); Foo.

If condition

Typically, the Pointcuts annotation has an empty method parameter list, returns a void value, and the method body is empty. But if the expression declares:

  • Args, target, this, and other parameters, you can declare an additional parameter list.
  • If, the method must be public static Boolean.

Take a look at sample MethodAspect8:

@Aspect
public class MethodAspect8 {
    @Pointcut("call(boolean *.*(int)) && args(i) && if()")
    public static boolean someCallWithIfTest(int i, JoinPoint jp) {
        // any legal Java expression...
        return i > 0 && jp.getSignature().getName().startsWith("setAge");
    }

    @Before("someCallWithIfTest(i, jp)")
    public void aroundMethodCall(int i, JoinPoint jp) {
        Log.e(TAG, "before if "); }}Copy the code

The annotation declared by the pointcut method someCallWithIfTest represents any method. This method returns a Boolean. The parameter signature is a single parameter of type int, followed by the if condition, indicating that the value of the int parameter is greater than 0, and the method signature begins with setAge.

This makes the execution of the aspect code dynamic, but it does not mean that pointcuts that do not meet the if condition will not be woven into the code. It will still weave, except that the someCallWithIfTest method is executed before the weave code is called. The weave code will only be executed if the return value is true.

Now that you know how it works, the if logic can actually be put into the weave point code, making it a little easier to understand.

Advice

There are five types of annotations in AspectJ: Before, After, AfterReturning, AfterThrowing, and Around. We collectively refer to them as Advice annotations.

Advice instructions
@Before Weave in before pointcut
@After Pointcut post-weave, regardless of join point execution, including normal return and throw exceptions
@AfterReturning It is executed only after the pointcut returns normally, matching all types if no return type is specified
@AfterThrowing Executed only after a pointcut throws an exception, matching all types if no exception type is specified
@Around Instead of the original pointcut, call if the original code is to be executedProceedingJoinPoint.proceed()

Advice There are some constraints to the approach to annotations:

  1. The method must be public.
  2. The return value of Before, After, AfterReturning, and AfterThrowing must be void.
  3. The goal of Around is to replace the original pointcut, which generally has a return value, which requires that the declared return value type be the same as the return value of the pointcut method; Does not work with other Advice. It will not work if you declare Before or After After a Pointcut Around.
  4. JoinPoint method signature can be an additional statement, JoinPointStaticPart, JoinPoint. EnclosingStaticPart.

JoinPoint, JoinPointStaticPart, JoinPoint. EnclosingStaticPart?

When executing the aspect code, AspectJ wraps the context information at the JoinPoint into JoinPoint for our use. Some of this information can be determined at compile time, such as method signature JoinPoint.getSignature (), joinPoint type JoinPoint.getKind (), Tangent point code position class name + rows joinPoint. GetSourceLocation () and so on, we will they collectively called JoinPointStaticPart.

Others are determined at run time, such as this, target, arguments, and so on.

  • JoinPoint contains static and dynamic information about the JoinPoint.
  • JoinPointStaticPart Static information about the connection point.
  • EnclosingStaticPart contains static information for the join point, which is the context for the join point.

If dynamic information is not required, static type parameters are recommended to improve performance.

With all this theory, it might seem complicated, but in fact the scenarios in our daily development are relatively simple.

Commonly used sample

  1. Bury all click events
@Aspect
public class MethodAspect5 {
    @Pointcut("execution(* android.view.View.OnClickListener+.onClick(..) )")
    public void callMethod() {
    }

    @Before("callMethod()")
    public void beforeMethodCall(JoinPoint joinPoint) {
        Log.e(TAG, "Buried point"); }}Copy the code

Android. View. The view. An OnClickListener + said an OnClickListener and its subclasses.

  1. MethodAspect3 uses Advice of the Around type, which is executed by dividing the argument by 10 before calling the run method.
@Aspect
public class MethodAspect3 {

    @Pointcut("execution(* com.wandering.sample.aspectj.Animal.run(..) )")
    public void callMethod() {
    }

    @Around("callMethod()"Public void aroundMethodCall(JoinEdingJoinPoint joinPoint) {Object[] args = JoinPoint.getargs (); int params = 0;for(Object arg : args) { params = (int) arg / 10; Proceed (new Object[]{params}); {value} the catch (Throwable Throwable) Throwable. PrintStackTrace (); }}}Copy the code

The Around method declaration ProceedingJoinPoint type instead of JoinPoint can be used to call JoinPoint code with its proceed method.

Existing problems with AspectJ

Repeat weaving, no weaving

If we wanted to weave buried statistics into the Activity lifecycle, we might write cut-point code like this.

@Pointcut("execution(* android.app.Activity+.on*(..) )")
public void callMethod() {}
Copy the code

Since the activity.class does not participate in packaging (Android.jar is on the Android device), the compatactivity is supported by libraries such as AppCompatActivity in support-v7 and activities defined in the project, which results in:

  1. If there is no copy lifecycle method in our business Activity, it will not be woven in.
  2. If our Activity inheritance tree overwrites the lifecycle methods, then all activities in the inheritance tree will be woven into the statistics code, which will result in duplicate statistics.

The solution is to define a base class Activity (such as BaseActivity) within the project, then replicate all lifecycle methods, and then refine the pointcut code to that BaseActivity.

@Pointcut("execution(* com.xxx.BaseActivity.on*(..) )")
public void callMethod() {}
Copy the code

But if you do, you’ll surely ask what else you need AspectJ for, hand.jpg.

Problems are difficult to troubleshoot

This is decided by the implementation of AOP technology, the process of modifying bytecode, no perception of the upper application, easy to hide the problem, the investigation is difficult. So if AOP is used in a project, documentation should be improved and co-developers informed.

Compilation time is longer

The Transform procedure, which iterates through all class files, looks for pointcuts that match the requirements, and inserts the bytecode. If the project is large and has a lot of woven code, it will increase the compilation time by about ten seconds.

As mentioned earlier, there are two ways to solve this problem:

  1. Use exclude to exclude package names that do not need to be woven.
  2. If weaving code does not need weaving in the Debug environment, such as burying, use Enabled False to turn off AspectJ.

compatibility

Unknown risks may arise if you use a tripartite library that also uses AspectJ.

For example, if Hugo is used in the SAMPLE project, the class in the project will not be entered into APK, and ClassNotFoundException will occur at runtime. This may have been caused by a conflict between the Hugo project Plugin and Hujiang’s AspectJX Plugin.

Android AspectJ’s Hugo project is the first of its kind in Android AspectJ.

Refer to the article

  1. AspectJ in Android series
  2. AOP AspectJ comprehensive anatomy in Android
  3. Android uses Aspectj to limit fast clicks
  4. AspectJ In Android Studio
  5. AspectJ for AOP technology learning
  6. Everything you need to know about AspectJ