preface

This article will explain AOP from another Angle, from the macro implementation principles and the nature of design. Most posts on AOP start with a list of syntax and then a demo application. But learning can not know how, do not know why.

I have a few thoughts on AOP: Why is AspectJ so hot? How does AspectJ work? How is it different from Spring AOP? In what situations? Can we implement an AOP method ourselves?

Before getting familiar with the principle, if you want to master the use of AOP can see:

  • Article read AOP | do you want the most comprehensive AOP method discussed in this paper
  • Most complete article apply AOP | selection considerations + analyze the classic open source library while practice, alacrity
  • The last piece of the puzzle | AOP AST abstract syntax tree — most lightweight methods of AOP

A, introducing

Type a small Demo to introduce the topic, assuming I don’t want to rely on any AOP methods and add logging printing before and after the execution of a particular method.

The first way: write dead code

Define a target class interface

The before() and after() methods are dead in the execute() body, which is pretty inelegant, so let’s fix that.

The second method: static proxy

But the question is, as the need to print logs increases and Proxy classes proliferate, can we keep just one Proxy? This is where we need to use the JDK dynamic proxy.

Third way: dynamic proxy

Create a dynamic proxy class

Client call

This raises the issue that log printing and business logic are coupled together, and we want to separate the front and back as separate enhancement classes.

Fourth way: dynamic proxy + separate enhanced classes

Create an enhanced class interface and an implementation class

Use reflection instead of write-dead methods to decouple agents and operators

Client call

But using reflection performance is poor, and dynamic proxy is not convenient to use, is there a better way?

We found problems with the Demo

  • Static proxy each time to create their own proxy class, too cumbersome, reuse and poor, a proxy can not simultaneously proxy multiple types;
  • Dynamic proxies can be reused, but their performance is poor;
  • The proxy class is coupled to the invocation phase of the proxy class, and if I need to change the names of the before and after methods, I might ignite a bomb.
  • If a proxy intercepts a class, it intercepts all methods of that class. Should I add an if-else method filter to the proxy class? Can we intercept only specific methods?
  • If I want to both print logs and calculate the method execution time, do I have to change the enhancement class every time?

Our appeal is simple: 1. High performance; 2. Loose coupling; 3. Convenient steps; High flexibility.

How do the major AOP frameworks address this problem? Let’s have a look!

Second, AOP method

Different AOP approaches work in slightly different ways, so let’s take a look at how AOP can be implemented:

AOP way mechanism instructions
Static weave Static agent Modify the original class directly, such as generating proxy class APT at compile time
Static weave Custom class loaders Start a custom class loader with a class loader and add a class loading listener that weaves in the cut logic represented by Javassist when it finds that the target class is loaded
Dynamic weave A dynamic proxy After the bytecode is loaded, the Proxy class is dynamically generated for the interface, and the facets are implanted into the Proxy class represented by JDK Proxy
Dynamic weave Dynamic bytecode generation After the bytecode is loaded, a class is subclassed by bytecode technology, and the method interception technique is used in the subclass to intercept the invocation logic of all the parent class methods. It is a subclass proxy represented by CGLIB

All AOP approaches are essentially intercept, proxy, reflection (in the dynamic case), and the implementation principle can be seen as a generalization of the proxy/decorator design pattern. Why? Let’s break it down.

Static weaving principle, using AspectJ as an example

The principle of static weaving is static proxy, and let’s take AspectJ as an example.

1. AspectJ design idea

How did AspectJ solve the problems with Demo? AspectJ provides two powerful sets of mechanisms:

(1) aspect of grammar | solve business and coupling section

Aspects, in AspectJ, solve this problem.

@Before("execution(* android.view.View.OnClickListener.onClick(..) )")
Copy the code

We can generate proxies by combining enhanced classes with intercepting matching conditions (pointcuts) through sections. This gives the decision of whether to use the aspect back to the aspect, and we can decide which methods of which classes will be propped while writing the aspect, logically without intruding into the business code.

The normal proxy pattern does not decouple the aspect from the business code. Although the logic of the aspect is separated into the proxy class, the right to decide whether to use the aspect is still in the business code. This led to all the trouble in the Demo.

AspectJ provides two sets of methods for describing aspects:

  1. The common method based on Java annotation section description is very convenient to write and compatible with Java syntax.
@Aspect
public class AnnoAspect {
    @Pointcut("execution(...) ")
    public void jointPoint() {
    }

    @Before("jointPoint()")
    public void before() {
		//...
    }

    @After("jointPoint()")
    public void after() {/ /... }}Copy the code
  1. The aspect file based approach to section description is incompatible with Java syntax.
public aspect AnnoAspect { pointcut XX(): execution(...) ; before():XX() {
        //...
    }
    after(): XX() {/ /... }}Copy the code
(2) woven into tools | solve agent tedious manual call

So the aspect syntax makes the aspect logically decoupled from the business code, but how do I find the specific business code woven into the aspect?

There are two approaches: one is to provide a registration mechanism, with additional configuration files indicating which classes are affected by the aspect, but this still interferes with the object creation process. Another solution is to scan the aspect at compile time or class load time and insert the aspect code into the business code in some way.

There are two AspectJ weaving methods: one is AJC compilation, which can weave facets into business code at compile time. The other is the Agent agent of AspectJweaver. Jar, which provides a Java Agent for weaving facets during class loading.

2. The AspectJ implementation mechanism is backtracked by class

(1)@Beforemechanism

International convention to write a Demo

  1. Custom AutoLog annotations

  1. Write LogAspect aspects

  1. Annotate pointcuts

After decomcompiling (please click on the larger image to view)

Discovering that AspectJ inserts the method that calls the aspect into the pointcut and encapsulates the method name of the pointcut, its class, input parameter name, input parameter value, return value, and so on, and passes that information to the aspect establishes the association between the aspect and the business code.

Logaspectof ().aroundJoinPoint(localJoinPoint); Find out.

What did we find? The insertion of Before and After intercepts the target JoinPoint by inserting the Advise method Before and After the matching JoinPoint call. As shown below:

(2)@Aroundmechanism
  1. Custom SingleClick annotations

  1. Write the SingleClickAspect section

  1. The business side annotates

Open the compiled class file (click on the larger image to view)

We found that weaving Before and After was different! The former simply inserts the Advise method before and after the matching JoinPoint, just insert. Around splits the business code from the Advise method, migrates the business code to a new function, and executes it through a separate closure split, which acts as a proxy for the target JoinPoint. So in the Around case, instead of writing the aspect logic, You also need to manually call joinPoint.proceed() to invoke the closure to execute the original method.

What does proceed() do

So what is this arc? When did you get it?

Continue to back

Within the AroundClosure, we pass in the run-time object and the current joinPoint object, call both ends of the linkClosureAndJoinPoint() binding, It can be used in Around ProceedingJoinPoint. Proceed () call AroundClosure, goes in the call to the target method.

So here’s a diagram summarizing the Around mechanism:

The logic to execute is obvious from AspectJ’s compiled class file. The PROCEED method is a callback to execute a method in the proxied class.

So AspectJ does the following:

  1. First take out all file names from the file list, read the file, for analysis;

  2. Scan section files with aspect;

  3. According to the rules defined in the section, intercept the matching JoinPoint;

  4. Continue reading the rules of the section definition, weaving in the section using different strategies, depending on around or before.

(3)@Before @AfterThe mechanism and@AroundMechanism of the difference between
  • Before and After are simply woven into the Advise method
  • Around uses the proxy + closure approach for replacement

3. Summary of AspectJ underlying technology

After analyzing class, you can see that AspectJ is essentially writing aspects in a specific language and compiling them through its own syntactic compiler, the AJC compiler, to generate a new proxy class that enhances the business classes.

  1. AspectJ is a code generation tool;

    Write generic code, define a set of code generation rules based on AspectJ syntax, and AspectJ inserts that code into place for you.

  2. AspectJ syntax is the syntax used to define code generation rules.

    Extend the compiler to introduce specific syntax to create Advise, thus weaving in the code for Advise at compile time.

    If you’ve used Java Compiler Compiler (JavaCC), you’ll see that the idea of code generation rules is strikingly similar. JavaCC allows you to add your own Java code to the syntax definition rules file to handle the various syntax elements read in.

Four, dynamic weaving principle, with Spring AOP as an example

The principle of dynamic weaving is dynamic proxy.

1. Implementation principle of Spring AOP

Spring AOP uses interception to decorate the propped class to replace the execution of the original object behavior without generating a new class.

2. Spring AOP VS AspectJ

Spring AOP uses AspectJ. How can it be a dynamic proxy?

That’s because Spring simply uses the same annotations as AspectJ, and instead of using AspectJ’s compiler, turns to the implementation principles of dynamic proxy technology to build Spring AOP’s internal mechanics (dynamic weaving), which is the fundamental difference from AspectJ (static weaving).

Spring’s underlying dynamic proxies are divided into JDK dynamic proxies and CGLib:

  1. JDK dynamic proxy is used to dynamically generate a class that implements the specified interface. Note that dynamic proxy has one constraint: the target object must have an interface. Without an interface, dynamic proxy cannot be implemented.

  2. CGLIB is used to proxy classes by loading the class file of the proxied object class and modifying its bytecode to generate a subclass that inherits the proxied class. Cglib is used to compensate for dynamic proxies.

3. JDK dynamic proxy principle

Our previous Demo used dynamic proxies in the third way, which left us wondering, how are dynamic proxy classes and their object instances generated? Why does calling a dynamic proxy object method call a target object method?

We use proxy. newProxyInstance to dynamically generate instances of the Proxy class for the specified interface. Let’s look at the internal implementation mechanism of newProxyInstance.

The proxy object implements all of the interface’s methods, which are handled by our custom handler.

Let’s look at how the getProxyClass0 method creates a proxy class with only one class loader and one interface.

Note that in Android, the dynamic proxy class is generated directly, whereas in Java, the bytecode of the proxy class is generated, and the proxy class is generated from the bytecode.

Then the client can getProxy() to get the generated proxy class com.sun.proxy.$Proxy0

This Proxy class inherits from Proxy and implements all the interfaces of our Proxy class. Inside each interface method, the Invoke method of InvocationHandlerImpl is called by reflection.

Summarize the following steps:

  1. The interface information of the proxied class is obtained, and a dynamic proxied class with the proxied interface is generated.
  2. Get the proxy class constructor by reflection;
  3. The constructor is used to generate an instance object of the dynamic proxy class, which is processed by calling the invokeHandler method before calling the concrete method.

Afterword.

1. The design mode cannot be separated from the business scenario

Unconsciously, we reviewed the agency pattern. Design patterns must depend on a large number of business scenarios, and it makes no sense to look at design patterns in isolation from business.

Even if you understand the content and structure of the pattern, you won’t be able to apply it at the right time because you’re out of the application scenario.

2. Dare to go for elegant code

First of all, you should dare to pursue elegant code, as we started with the need to print logs, constantly asking questions, constantly seeking better solutions, digging new problems on new solutions… If you don’t pursue design at all, you certainly don’t want to study design patterns.

This piece took 26 tomato minutes (650 minutes)


I’m FeelsChaotic, a programmer who can write code, cut video and draw pictures. I’m committed to the pursuit of code elegance, architecture design and T-shaped growth.

Feel free to follow FeelsChaotic’s short books and nuggets, and if my articles are even remotely helpful to you, please feel free to ❤️! Your encouragement is my biggest motivation to write!

The most important, please give your suggestions or opinions, there are mistakes please correct!