One, a brief introduction
1. Concept of AOP
If you do backend development in Java, you know the concept of AOP. If you don’t know, it’s ok to use baidu Baike’s introduction to let you know what this thing is:
AOP for the abbreviation of Aspect Oriented Programming, meaning: section-oriented Programming, through pre-compilation and runtime dynamic proxy to achieve unified maintenance of program functions of a technology. AOP is a continuation of OOP, a hot topic in software development, and an important content in Spring framework. It is a derivative paradigm of functional programming. Using AOP, each part of the business logic can be isolated, thus reducing the degree of coupling between each part of the business logic, improving the reusability of the program, and improving the efficiency of development.
2. Project scenario
Project development process, there may be such a demand, we need to in the method, after the completion of execution logs (more common in the background and development ~), or the method to calculate the execution time, in the case of not using AOP, we can approach the last call another special logging method, or at the beginning and the end of the method body respectively for time, Then calculate how long it takes to execute the entire method by calculating the time difference, which also completes the requirement. What if there’s more than one way to play it? Does each method write the same piece of code? What if the post-processing logic changes? Finally, the boss said we have to delete this feature one by one?
Obviously, this is not possible, we are not only code porters, we are thinking software development engineers. Such a low approach is absolutely not to do, this problem we can use AOP to solve, is not the method before and after the method to insert a piece of code? AOP can be done in minutes.
3. Implementation of AOP
Keep in mind that AOP is just a concept, and there are several ways to implement it (tools and libraries) :
- AspectJ: A seamless extension to faceted programming for the JavaTM language (for Android).
- Javassist for Android: An Android platform port of Javassist, a well-known Java class library for bytecode manipulation.
- DexMaker: Dalvik virtual machine, a Java API that generates code at compile time or run time.
- ASMDEX: a bytecode operation library similar to ASM. It runs on the Android platform and operates on Dex bytecode.
This article focuses on AspectJ, so let’s take a look at how AspectJ-style AOP can be used in Android development.
Introduction of AspectJ
The introduction of Eclipse is not the same as the introduction of Android Studio. In this article, we will introduce AspectJ to Android Studio. Android Studio needs to be introduced in the build.gradle file of the app module. There are three steps:
1) Add core dependencies
dependencies { ... The compile 'org. Aspectj: aspectjrt: 1.8.9'}Copy the code
2) Write gradle compilation scripts
Dependencies buildscript {repositories {mavenCentral ()} {the classpath 'org. Aspectj: aspectjtools: 1.8.9' classpath 'org. Aspectj: aspectjweaver: 1.8.9'}}Copy the code
AspectJ relies on maven repositories.
3) Add gradle tasks
dependencies { ... } // The code above is useless to illustrate: The mission of the code below and dependencies at the same level import org. Aspectj. Bridge. Called IMessage -- the import org. Aspectj. Bridge. The MessageHandler import org.aspectj.tools.ajc.Main final def log = project.logger final def variants = project.android.applicationVariants variants.all { variant -> if (! variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") return; } JavaCompile JavaCompile = variant. JavaCompile 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; }}}}Copy the code
Paste it directly into the end of the build.gradle file. Do not embed it in other commands.
Basic knowledge of AOP
Before using AspectJ, it is still necessary to cover the basics of AOP, which can be skipped by those familiar with it.
1. AOP terminology
- Advice: The functionality you want, namely logging, time calculation, and so on.
- Joinpoints: There are a lot of places where you can give Advice, basically before, after, or when an exception is thrown (Spring only supports method join points). AspectJ also allows you to do constructor or property injection, though you won’t normally do that, just remember that everything that’s associated with a method is join points.
- Pointcut: Mentioned above, on the basis of the connection point to define the starting point, a class, you have 15 method, that is of a dozen points right, but you don’t want to use in all methods attachment notice (use weave, besides below), you just want to let a few, before and after the call this a few method or do something when an exception is thrown, Define these methods with pointcuts, and let pointcuts filter join points to select the methods you want.
- Aspect: An Aspect is a combination of advice and pointcuts. Now it is found that there is no connection point what thing, connection point is to make you understand the cut point out, understand this concept on the line. Advice says what to do and when (AOP annotations like before, after, around), while pointcuts say where to do (specifying exactly which method), which is a complete aspect definition.
- Weaving is the process of creating a new proxy object by applying a slice to a target object.
The explanation of the above terms is taken from the article “Conceptual advice, pointcuts, facets in AOP.” The author’s description is straightforward and easy to understand. Thumbs up.
2. AOP annotations and usage
- @aspect: Declare the Aspect, mark the class
- Pointcut(Pointcut expression) : Defines pointcuts and marks methods
- @before (pointcut expression) : pre-notification, executed Before pointcut
- @around (pointcut expression) : Around the notification, executed before and after the pointcut
- @after (pointcut expression) : post-notification, executed After pointcut
- AfterReturning(pointcut expression) : returns notification, executed after the pointcut method returns results
- AfterThrowing(pointcut expression) : exception notification, executed when the pointcut throws an exception
@pointcut, @before, @around, @after, @afterRETURNING, and @AfterThrowing need to be used in Aspect classes, that is, in classes that use @aspect.
1) What is the tangent point expression?
This is the execution (* com.lqr.. *. * (..) ). The composition of the pointcut expression is as follows:
Execution (< modifier pattern >? < return type mode > < method name mode >(< parameter mode >) < exception mode >?Copy the code
All items are optional except return type mode, method name mode, and parameter mode. Modifier modes are public, private, and protected, and exception modes are NullPointException, etc.
The understanding of pointcut expressions is not the focus of this article. Here are a few examples to illustrate:
@Before("execution(public * *(..) )") public void before(JoinPoint point) { System.out.println("CSDN_LQR"); }Copy the code
Match all public methods and print “CSDN_LQR” before the method is executed.
@Around("execution(* *to(..) )") public void around(ProceedingJoinPoint joinPoint) { System.out.println("CSDN"); joinPoint.proceed(); System.out.println("LQR"); }Copy the code
Matches all methods ending in “to”, printing “CSDN” before method execution and “LQR” after method execution.
@After("execution(* com.lqr.. *to(..) )") public void after(JoinPoint point) { System.out.println("CSDN_LQR"); }Copy the code
Match methods that end in “to” in the com.lqr package and its subpackages and print “CSDN_LQR” after the method is executed.
@AfterReturning("execution(int com.lqr.*(..) )") public void afterReturning(JoinPoint point, Object returnValue) { System.out.println("CSDN_LQR"); }Copy the code
Match all methods in the com. LQR package that return type int and print “CSDN_LQR” after the result of the method.
@AfterThrowing(value = "execution(* com.lqr.. * (..) )", throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("ex = " + ex.getMessage()); }Copy the code
Matches all methods in the com.lQR package and its subpackages and prints “ex = error message “when the method throws an exception.
2) The use of @pointcut
Pointcut is specifically used to define pointcuts so that Pointcut expressions can be reused.
You may need to do some actions (such as logging when an error occurs) before the pointcut executes and when the pointcut raises an exception. This can be done:
@Before("execution(* com.lqr.. * (..) )") public void before(JoinPoint point) { System.out.println("CSDN_LQR"); } @AfterThrowing(value = "execution(* com.lqr.. * (..) )", throwing = "throwing ") public void afterThrowing(Throwable ex) {system.out.println (" throwing "); }Copy the code
As you can see, the expression is the same, so how do you reuse that expression? This requires the @pointcut annotation, which is annotated on an empty method, such as:
@Pointcut("execution(* com.lqr.. * (..) )") public void pointcut() {}Copy the code
At this point, “pointcut()” is equivalent to “execution(* com.lqr.. * (..) )”, then the above code can be changed like this:
@Before("pointcut()") public void before(JoinPoint point) { System.out.println("CSDN_LQR"); } @AfterThrowing(value = "pointcut()", Throwing = "throwing ") public void afterThrowing(Throwable ex) {system.out.println (" throwing "); }Copy the code
Four, in actual combat
So now it’s time to do it, so let’s do a simple example.
1, the point of tangency
This is a button click event on the interface, just a simple method, we use it to try the knife.
public void test(View view) {
System.out.println("Hello, I am CSDN_LQR");
}
Copy the code
2, section class
To weave a section of code before and after the target class method, we must have a section class. Here is the section class code:
@Aspect
public class TestAnnoAspect {
@Pointcut("execution(* com.lqr.androidaopdemo.MainActivity.test(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint point) {
System.out.println("@Before");
}
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around");
}
@After("pointcut()")
public void after(JoinPoint point) {
System.out.println("@After");
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint point, Object returnValue) {
System.out.println("@AfterReturning");
}
@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowing(Throwable ex) {
System.out.println("@afterThrowing");
System.out.println("ex = " + ex.getMessage());
}
}
Copy the code
3. Execution results of the notices
Let’s see how these annotations work out.
“Hello, I am CSDN_LQR” is printed in the button click event, but not here.
Because the @around wrap notification intercepts the execution of the original method content, we need to manually pass it through. The code is modified as follows:
@Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("@Around"); joinPoint.proceed(); // The target method completes}Copy the code
Also wrong, missing @afterthrowing notification. This notification will only be executed if the pointcut throws an exception. We can make our code appear with a simple runtime exception:
public void test(View view) {
System.out.println("Hello, I am CSDN_LQR");
int a = 1 / 0;
}
Copy the code
In this case, the @AfterThrowing notification is indeed called, and an error message (Divide by Zero) is printed. However, the @AfterRETURNING notification will not be executed for the simple reason that a pointcut cannot return a result if an exception is thrown. In other words: @afterthrowing and @AfterRETURNING are in conflict and cannot occur simultaneously at the same cut point.
4. Implementation of method time-consuming calculation
Since @around is a wrap Around advice, you can perform operations before and after the pointcut. AspectJ needs to manually execute joinPoint.proceed() in the @around advice to make sure that the pointcut has been executed, so it can be sure that the operation is before or after the pointcut. Therefore, code before joinPoint.proceed() will be executed before pointcut execution, and code after JoinPoint.proceed () will be executed after pointcut execution. Thus, the implementation of method time calculation is as simple as this:
@Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { long beginTime = SystemClock.currentThreadTimeMillis(); joinPoint.proceed(); long endTime = SystemClock.currentThreadTimeMillis(); long dx = endTime - beginTime; System.out.println(" time: "+ dx + "ms"); }Copy the code
5. The role of JoinPoint
All the above notifications carry at least one JointPoint parameter. This parameter contains all the information about the pointcut.
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String name = signature.getName(); // Method name: test Method Method = signature.getMethod(); / / methods: public void com.lqr.androidaopdemo.MainActivity.test(android.view.View) Class returnType = signature.getReturnType(); / / the return value type: void Class declaringType = signature. GetDeclaringType (); / / method in the class name: MainActivity String [] parameterNames = signature. GetParameterNames (); / / parameter name: the view Class [] parameterTypes = signature. GetParameterTypes (); // Parameter type: ViewCopy the code
6, notes cut point
The previous pointcut expression looks like this:
Execution (< modifier pattern >? < return type mode > < method name mode >(< parameter mode >) < exception mode >?Copy the code
But in fact, the above tangent point expression structure is not complete, should look like this:
Execution (<@ annotation type pattern >? < modifier mode >? < return type mode > < method name mode >(< parameter mode >) < exception mode >?Copy the code
This means that pointcuts can be annotated.
1) Custom annotations
If we use annotations to mark pointcuts, we usually use custom annotations so that we can expand.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnoTrace {
String value();
int type();
}
Copy the code
- @target (ElementType.METHOD) : indicates that this annotation can only be annotated on methods. If you want to classes and methods can be used, it can be so write: @ Target ({ElementType METHOD, ElementType. TYPE}), and so on.
- @Retention(retentionPolicy.runtime) : indicates that the annotation is visible at RUNTIME (and that SOURCE and CLASS specify which level the annotation is visible at.RUNTIME is used).
Value and Type are self-extended attributes to store additional information.
2) Use custom annotations to mark pointcuts
This custom annotation can only be annotated on methods (except for constructors, which are also called constructors and need to use elementType.constructor). Use it as you would any other annotation:
@TestAnnoTrace(value = "lqr_test", type = 1)
public void test(View view) {
System.out.println("Hello, I am CSDN_LQR");
}
Copy the code
3) Annotated pointcut expression
Since we use annotations to mark pointcuts, the pointcut expression must be different.
@Pointcut("execution(@com.lqr.androidaopdemo.TestAnnoTrace * *(..) )") public void pointcut() {}Copy the code
Point of tangency expressions using annotations, must be @ + annotate the full path, such as: @ com. LQR. Androidaopdemo. TestAnnoTrace.
Pro test available, no texture.
4) Get the value of the annotation property
We declared two properties, value and type, when we wrote the custom annotation, and assigned them values when we used the annotation. How do we get the values of these two properties in the notification? Remember the JoinPoint parameter, which gets the value of the attribute in the annotation as follows:
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); TestAnnoTrace annotation = method.getannotation (testannotrace.class); String value = annotation.value(); int type = annotation.type();Copy the code
Finally, paste the Demo address
Github.com/GitLqr/Andr…