Spring is the most popular framework in Java due to the IOC and AOP capabilities it provides. This article discusses the implementation of Spring AOP. The first section introduces concepts related to AOP, which you can skip if you are familiar with, and the second section introduces how Spring implements the concepts of AOP with source code.

1. The concept of AOP

1.1 JoinPoint

The program execution point for weaving.

Common types:

  • Method Call: The point at which a Method is called.

  • Method Call Execution: The point at which Execution within a Method begins.

    Method invocation is the point of execution on the calling object, and method invocation execution is the point at which the method of the called object begins execution.

  • Constructor Call: The point at which an object’s Constructor is called.

  • Constructor Call Execution: The point at which Execution begins within an object Constructor.

  • Field Set: The point at which a Field is Set either by setter methods or directly.

  • Field Get: The point at which a Field is accessed through a getter or directly.

  • Exception Handler Execution: The point at which Exception handling logic is executed after some type of Exception is thrown.

  • Class Initialization: The time at which certain static types or blocks of a Class are initialized.

1.2 Pointcut

Jointpoint description.

Common expressions:

  • Specify the name of the method where Joinpoint resides
  • Regular expression
  • A specific Pointcut presentation language

1.3 Advice

The carrier of single crosscutting concern logic, woven into Joinpoint crosscutting logic.

Specific form:

  • Before Advice: Before Joinpoint.
  • After Advice: After Joinpoint, it is divided into three types:
    • After Returning Advice: Return to Joinpoint After Returning.
    • After Throwing Advice: Executes After Throwing an exception at Joinpoint.
    • After Finally Advice: Run this command After Joinpoint completes normally or throws an exception.
  • Around Advice: Wraps Joinpoint, executes Before and After Joinpoint, with Before Advice and After Advice functions.
  • Introduction: Add new properties or behaviors to existing objects.

1.4 the Aspect

An AOP conceptual entity that modularizes the crosscutting concern logic, containing definitions for multiple Pointcuts and related Advice.

1.5 Weaving and weaving machine

Weaving: Integrating Aspect modular crosscutting concerns into AN OOP system.

Weaver: Used to complete the weaving operation.

1.6 the Target

Objects that are woven into crosscutting logic during weaving.

Put the six concepts together, as shown below:

Now that you’ve looked at the various concepts of AOP, I’ll look at the implementation of AOP concepts in Spring.

2. Implementation in Spring

As mentioned earlier, AOP Joinpoint has several types: method call, method execution, field setting, field retrieval, and so on. In Spring AOP, only Joinpoint of the method execution type is supported, but this already meets 80% of your development needs. If you have special needs, you can turn to other AOP products, such as AspectJ. Since Joinpoint involves run-time processes, it is the last step in putting together all the pieces to get AOP running. So I will introduce the Joinpoint implementation after other concept implementations.

2.1 Pointcut

Since Spring AOP only supports Joinpoint of method execution categories, Pointcut needs to define the methods that are woven into it, and because Java methods are wrapped in classes, Pointcut needs to define classes and methods that are woven into it. See the implementation below.

Spring with org. Springframework. Aop) Pointcut interface definition at the top of Pointcut abstraction.

public interface Pointcut {
    
   // ClassFilter is used to match the woven class
   ClassFilter getClassFilter(a);

   // MethodMatcher is used to match methods that are woven in
   MethodMatcher getMethodMatcher(a);

   // TruePoincut singleton, which matches all classes and methods by default
   Pointcut TRUE = TruePointcut.INSTANCE;
}
Copy the code

As you can see, Pointcut defines the corresponding Joinpoint through a combination of ClassFilter and MethodMatcher. Pointcut defines classes and methods separately for reuse. For example, if there are two JoinPoints, fun() of class A and Fun () of class B, and the two methods have the same signature, then only one fun() MethodMatcher object is needed to achieve reuse. The same is true for ClassFilter.

Let’s see how ClassFilter and MethodMatcher match.

ClassFilter matches the knitted class using the matches method as follows:

public interface ClassFilter {
    
    // Matches the woven class, returning true on success and false on failure
    boolean matches(Class
        clazz);

    // TrueClassFilter singleton, matches all classes by default
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
Copy the code

MethodMatcher also matches methods that are woven in using matches:

public interface MethodMatcher {
    
   // Match the method to be woven, returning true on success and false on failure
   // Do not consider specific method parameters
   boolean matches(Method method, Class
        targetClass);
    
   // Match the method to be woven, returning true on success and false on failure
   // Consider the specific method parameters and check the parameters for matching
   boolean matches(Method method, Class
        targetClass, Object... args);
   
   // a flag method
   // false matches the first matches method, regardless of the parameters
   // true takes the argument into account, using the second matches method
   boolean isRuntime(a);

   // a singleton object for TrueMethodMatcher that matches all methods by default
   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
Copy the code

Does it seem odd to see the matches method declaration that matches already matches the Class in ClassFilter? So why does the matches method on MethodMatcher have a Class
targetClass parameter. Notice that the Class
type parameters will not be matched, but only to find specific methods. Such as:

public boolean matches(Method method, Class
        targetClass) { Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); . }Copy the code

In MethodMatcher there are two matches methods in particular compared to ClassFilter. The result of isRuntime() will determine which to call. MethodMatcher is divided by isRuntime() into two abstract classes StaticMethodMatcher (which returns false, regardless of parameters) and DynamicMethodMatcher (which returns true, regardless of parameters).

Pointcut for MethodMathcer can be divided into StaticMethodMatcherPointcut and DynamicMethodMatcherPointcut, relevant class diagram as shown below:

DynamicMethodMatcherPointcut will not be introduced in this paper, mainly introduces three listed in the class diagram under the implementation class.

(1) NameMatchMethodPointcut

By specifying a method name and then matching it directly, the “*” wildcard character is also supported.

public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable {
    
    // Method name
    private List<String> mappedNames = new ArrayList<>();

    // Set method name
    public void setMappedNames(String... mappedNames) {
        this.mappedNames = new ArrayList<>(Arrays.asList(mappedNames));
    }


    @Override
    public boolean matches(Method method, Class
        targetClass) {
        for (String mappedName : this.mappedNames) {
            IsMatch supports "*" wildcard characters based on method names
            if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
                return true; }}return false;
    }
    
    // ...
}
Copy the code

(2) JdkRegexpMethodPointcut

There is an internal Pattern array that matches the method name by specifying a regular expression.

(3) the AnnotationMatchingPointcut

Matches based on whether the target object has an annotation of the specified type.

2.2 Advice

Advice is the carrier of crosscutting logic. The interface class diagram for Advice in Spring AOP is as follows:

(1) the MethodBeforeAdvice

The crosscutting logic is executed before the Joinpoint method. Can be used for resource initialization or preparation work.

public interface MethodBeforeAdvice extends BeforeAdvice {
    
    void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
    
}
Copy the code

Let’s implement a MethodBeforeAdvice to see how it works.

public class PrepareResourceBeforeAdvice implements MethodBeforeAdvice {
    
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Prepare resources"); }}Copy the code

Define an ITask interface:

public interface ITask {
    
    void execute(a);
    
}
Copy the code

ITask’s implementation class MockTask:

public class MockTask implements ITask {
    
   @Override
   public void execute(a) {
      System.out.println("Start the mission.");
      System.out.println("Mission accomplished."); }}Copy the code

Use ProxyFactory to get proxy classes. Advisor is used to encapsulate Pointcut and Advice.

public class Main {
    
   public static void main(String[] args) {
      MockTask task = new MockTask();
      ProxyFactory weaver = new ProxyFactory(task);
      weaver.setInterfaces(new Class[]{ITask.class});
      // Contains a NameMatchMethodPointcut
      NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
      // Specify the method name of NameMatchMethodPointcut
      advisor.setMappedName("execute");
      / / specify the Advice
      advisor.setAdvice(newPrepareResourceBeforeAdvice()); weaver.addAdvisor(advisor); ITask proxyObject = (ITask) weaver.getProxy(); proxyObject.execute(); }}/** output: Prepare resources. Start task execution. Task completion
Copy the code

It can be seen that the execute proxyObject proxy objects in the implementation of the method, first performed in PrepareResourceBeforeAdvice before method.

(2) the ThrowsAdvice

The crosscutting logic is executed when the Joinpoint method throws an exception. It can be used to monitor anomalies.

The ThrowsAdvice interface does not define any methods, but it is agreed that when implementing this interface, the methods defined must comply with the following rules:

void afterThrowing([Method, args, target], ThrowableSubclass)
Copy the code

The first three parameters are related to Joinpoint and can be omitted. ThrowableSubclass specifies the type of exception to intercept.

For example, you can define multiple afterThrowing methods to catch exceptions:

public class ExceptionMonitorThrowsAdvice implements ThrowsAdvice {
    
    public void afterThrowing(Throwable t) {
        System.out.println("[Ordinary exception] occurs");
    }
    
    public void afterThrowing(RuntimeException e) {
        System.out.println("[Runtime exception] occurred");
    }
    
    public void afterThrowing(Method m, Object[] args, Object target, ApplicationException e) {
        System.out.println(target.getClass() + m.getName() + "[Application exception] occurs"); }}Copy the code

Modify the contents of MockTask:

public class MockTask implements ITask {
    
    @Override
    public void execute(a) {
        System.out.println("Start the mission.");
        // Throw a custom application exception
        throw new ApplicationException();
        // system.out.println (" Task completed ");}}Copy the code

Modify the contents of Main:

public class Main {
    
    public static void main(String[] args) {
        MockTask task = new MockTask();
        ProxyFactory weaver = new ProxyFactory(task);
        weaver.setInterfaces(new Class[]{ITask.class});
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("execute");
        // Specify exception monitoring Advice
        advisor.setAdvice(newExceptionMonitorThrowsAdvice()); weaver.addAdvisor(advisor); ITask proxyObject = (ITask) weaver.getProxy(); proxyObject.execute(); }}/ * * the output: began to perform a task class. Com chaycao. Spring. Aop. * * / application exception 】 【 MockTaskexecute occur
Copy the code

When an ApplicationException is thrown, it is caught by the corresponding afterThrowing method.

(3) the AfterReturningAdvice

The crosscutting logic will be executed when the Joinpoint method returns normally. Can be used to handle resource cleanup.

public interface AfterReturningAdvice extends AfterAdvice {
    
   void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
    
}
Copy the code

Implement a resource cleanup Advice:

public class ResourceCleanAfterReturningAdvice implements AfterReturningAdvice {
    
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
      System.out.println("Resource Cleanup"); }}Copy the code

Modify MockTask for normal execution success, modify the Main method for a given ResourceCLeanAfterReturningAdvice, results are as follows:

/** output: Start executing the task task completed resource clearing **/
Copy the code

(4) MethodInterceptor

It is equivalent to Around Advice and is very powerful. It can be executed before and after Joinpoint methods, and even modify the return value. Its definition is as follows:

public interface MethodInterceptor extends Interceptor {
    
    Object invoke(MethodInvocation invocation) throws Throwable;
    
}
Copy the code

MethodInvocation is a wrapper for Method invocation; the Method is called with PROCEED (). Here’s an example:

public class AroundMethodInterceptor implements MethodInterceptor {

   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("Prepare resources");
      try {
         return invocation.proceed();
      } catch (Exception e) {
         System.out.println("Abnormal monitoring");
         return null;
      } finally {
         System.out.println("Resource Cleanup"); }}}Copy the code

The invoke method implemented above implements all three of these functions in one fell swoop.

The above four types of Advice apply to all instances of the target object class and are known as per-class types of Advice. There is also a per-instance type of Advice that adds new properties or behaviors to an instance, called Introduction in the first section.

(5) the Introduction

Spring adds new properties or behavior to the target object by declaring the interface and its implementation class, and then weaving the definition of the interface and the implementation of the implementation class into the target object through interceptors. We know the DelegatingIntroductionInterceptor, as an interceptor, when the new behavior, will appoint (delegate) to the implementation class to complete.

For example, if you want to reinforce the original MockTask without modifying the class’s declaration, you can declare a new interface, IReinfore:

public interface IReinforce {
   String name = "Enhancer";
   void fun(a);
}
Copy the code

Declare an implementation class for the interface:

public class ReinforeImpl implements IReinforce {

    @Override
    public void fun(a) {
        System.out.println("I'm strong enough to perform fun."); }}Copy the code

Modify the Main method:

public class Main {
   
   public static void main(String[] args) {
      MockTask task = new MockTask();
      ProxyFactory weaver = new ProxyFactory(task);
      weaver.setInterfaces(new Class[]{ITask.class});
      // Specify an instance of the implementation class to delegate to the interceptor
      DelegatingIntroductionInterceptor delegatingIntroductionInterceptor =
            new DelegatingIntroductionInterceptor(new ReinforeImpl());
      weaver.addAdvice(delegatingIntroductionInterceptor);
      ITask proxyObject = (ITask) weaver.getProxy();
      proxyObject.execute();
      // Use the IReinfore interface to invoke new properties and behaviors
      IReinforce reinforeProxyObject = (IReinforce) weaver.getProxy();
      System.out.println("By use"+ reinforeProxyObject.name); reinforeProxyObject.fun(); }}By using the enhancer I am strong enough to execute the fun method **/
Copy the code

The proxyObject, proxyObject, uses the interceptor to implement class methods using ReinforeImpl.

2.3 the Aspect

Spring uses advisors to represent aspects, the difference being that advisors typically hold only a Pointcut and an Advice. Advisors are categorized into PointcutAdvisor and IntroductionAdvisor according to Advice.

2.3.1 PointcutAdvisor

Common PointcutAdvisor implementation classes are:

(1) DefaultPointcutAdvisor

The most generic implementation class that can specify any type of Pointcut and any type of Advice except Introduction.

Pointcut pointcut = ... ;// Pointcut of any typeAdvice advice = ... ;// Any type of Advice except Introduction
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);
Copy the code

(2) NameMatchMethodPointcutAdvisor

As briefly described in the code demonstrating Advice, there is an internal instance of NameMatchMethodPointcut that can hold any type of Advice except Introduction.

Advice advice = ... ;// Any type of Advice except Introduction
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(advice);
Copy the code

(3) the RegexpMethodPointcutAdvisor

There is an internal instance of RegexpMethodPointcut.

2.3.2 IntroductionAdvisor

Only class-level interception and introduction-type Advice are supported. There are DefaultIntroductionAdvisor implementation class.

DelegatingIntroductionInterceptor introductionInterceptor =
				new DelegatingIntroductionInterceptor(new ReinforeImpl());
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(introductionInterceptor, IReinforce.class);
Copy the code

2.4 Weavers and weavers

In the code demonstrating Advice, we use ProxyFactory as the weaver

MockTask task = new MockTask();
/ / weave
ProxyFactory weaver = new ProxyFactory(task);
weaver.setInterfaces(new Class[]{ITask.class});
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PrepareResourceBeforeAdvice());
weaver.addAdvisor(advisor);
// weave in to return the proxy object
ITask proxyObject = (ITask) weaver.getProxy();
proxyObject.execute();
Copy the code

ProxyFactory generates proxy objects in the following ways:

  • If the target class implements some interface, it is generated by dynamic proxy by default.
  • If the target class does not implement an interface, it is generated by CGLIB by default.
  • Can also beSet up directlyProxyFactoryGenerated in such a way that CGLIB can be used even if the interface is implemented.

In the previous demo code, we did not start the Spring container, that is, we did not use the Spring IOC functionality, but used Spring AOP independently. So how does Spring AOP integrate with Spring IOC? Is based on the most common Spring integration approach, FactoryBeans.

ProxyFactoryBean inherits ProxyFactory’s parent ProxyCreatorSupport and has the ability to create proxy classes. It also implements the FactoryBean interface. When the Bean is obtained through getObject, You get the proxy class.

2.5 the Target

In the previous demo code, we directly specified an object as Target for the ProxyFactory. Not only can this be used in ProxyFactoryBean, but it can also be specified as a TargetSource.

TargetSource encapsulates the object, and ProxyFactoryBean gets the target object via the getTarget method of TargetSource. The getTarget method is then used to control which target objects are obtained. Several implementation classes of TargetSource are:

(1) SingletonTargetSource

Very simple, internal only holds a target object, directly returns. It’s the same as if we were specifying the object directly.

(2) PrototypeTargetSource

A new target object instance is returned each time.

(3) the HotSwappableTartgetSource

The runtime dynamically replaces the concrete implementation of the target object class based on specific conditions. For example, when one data source is down, you can switch to another.

(4) CommonsPool2TargetSource

Returns a finite number of target object instances, similar to a pool of objects.

(5) ThreadLocalTargetSource

Provide different target objects for different thread calls

2.6 Joinpoint

Finally to the final Joinpoint, we through the following example to understand the working mechanism of Joinpoint.

MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
weaver.setInterfaces(new Class[]{ITask.class});
PrepareResourceBeforeAdvice beforeAdvice = new PrepareResourceBeforeAdvice();
ResourceCleanAfterReturningAdvice afterAdvice = new ResourceCleanAfterReturningAdvice();
weaver.addAdvice(beforeAdvice);
weaver.addAdvice(afterAdvice);
ITask proxyObject = (ITask) weaver.getProxy();
proxyObject.execute();

/** output Preparing resources Start executing tasks Task completion Clearing resources **/
Copy the code

GetProxy generates an interface class for ITask via dynamic proxy. Before advice is executed before task is executed. How about afterAdvice’s after method?

The answer lies in the generated proxy class. In a dynamic proxy, the logic for the invocation of the proxy class method is determined by the Invoke method of the InvocationHandler instance, and the answer is further locked to the Invoke method.

. In this case, ProxyFactory getProxy invokes JdkDynamicAopProxy. GetProxy access proxy class.

// JdkDynamicAopProxy
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } Class<? >[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
Copy the code

Pass this to getProxy for newProxyInstance’s InvocationHandler parameter, i.e. JdkDynamicAopProxy is an implementation of InvocationHandler, Its invoke method looks like this:

// JdkDynamicAopProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // Get the specified advice through the advised (initialized when the object is created)
    // Encapsulates advice with the corresponding MethodInterceptor
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

    if (chain.isEmpty()) {
        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
    }
    else {
        // Create a MethodInvocation
        MethodInvocation invocation =
            new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        // Call procced to start the chain of interceptors (executing target object methods and advice for MethodInterceptor)
        retVal = invocation.proceed();
    }
    return retVal;
}
Copy the code

You first get the specified advice, which contains beforeAdvice and afterAdvice instances, but encapsulates a layer with MethodInterceptor for subsequent intercepting chains.

To create a RelectiveMethodInvocation object, and finally through the proceed into the interceptor chain.

RelectiveMethodInvocation Joinpoint in the Spring AOP is an implementation, the class diagram is as follows:

First look at the constructor of the RelectiveMethodInvocation:

protected ReflectiveMethodInvocation( Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, @Nullable Class
        targetClass, List interceptorsAndDynamicMethodMatchers) {
    this.proxy = proxy;
    this.target = target;
    this.targetClass = targetClass;
    this.method = BridgeMethodResolver.findBridgedMethod(method);
    this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
    this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
Copy the code

You do some assignments to the relevant properties, then look at the PROCEED method, how to call the target object and the interceptor.

public Object proceed(a) throws Throwable {
    // currentInterceptorIndex starts from -1
    // When all interceptors have been invoked, invokeJoinpoint invokes the target object's method
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // Get the interceptor and call its invoke method
    / / currentInterceptorIndex plus 1
    Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
Copy the code

CurrentInterceptorIndex starting from 1, interceptorsAndDynamicMethodMatchers has two interceptors, again due to minus 1, All calls to target object methods are conditional on currentInterceptorIndex being equal to 1.

First of all, minus 1 factorial. = 1, get contains beforeAdvice MethodBeforeAdviceInterceptor instance, currentInterceptorIndex add 1 to 0. Call its invoke method, which, since it is before-advice, executes the Before method of beforeAdvice, and then calls PROCEED to the next ring in the interceptor chain.

// MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}
Copy the code

Back to the proceed method, 0! = 1, again to get advice, this is include afterAdvice AfterReturningAdviceInterceptor instance, currentInterceptorIndex + 1 to 1. Its invoke method, since it is After-RETURNING -Adivce, executes proceed to the next ring in the interceptor chain.

// AfterReturningAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    Object retVal = mi.proceed();
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    return retVal;
}
Copy the code

Proceed again to the proceed method, 1 == 1, all interceptors have been called and the target object’s method will be executed. Then return returns to invoke and calls afterReturning of afterAdvice.

So in Joinpoint implementation, the target object method and Advice are executed in sequence through the MethodInterceptor.

3. Summary

After understanding the implementation of Spring AOP, the concept of AOP becomes clearer to the author. In the process of learning the author is most interested in Joinpoint interception chain, at first I do not know how to achieve, feel very magical 😲. The invocation of the interceptor’s invoke method and the MethodInvocation. Proceed method (to the next interceptor) call each other. That seems to be it. 😛