In previous articles we examined the process of creating and filtering notifiers and AOP creating proxy objects. Now that proxy objects are in place, let’s look at how the notifier logic is implemented.

preface

By reading the article, you can learn the following questions:

  • How does notification work?
  • What is the order in which multiple notifications are executed?
  • What is the order in which multiple notifications from multiple facets are executed?
  • Why doesn’t calling this class method work with @Transactional annotation?

We can look at it with these questions in mind

A, the invoke ()

ObjenesisCglibAopProxy ObjenesisCglibAopProxy ObjenesisCglibAopProxy ObjenesisCglibAopProxy JdkDynamicAopProxy implements the InvocationHandler interface. Let’s look at the invoke() method:

//JdkDynamicAopProxy.java

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	MethodInvocation invocation;
	Object oldProxy = null;
	boolean setProxyContext = false;

	TargetSource targetSource = this.advised.targetSource;
	Object target = null;

	try {
		// If the target object does not define equals(), it will be called without enhancement
<1>		if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
			// The target does not implement the equals(Object) method itself.
			return equals(args[0]);
		}
		// If the target object does not define a hashCode() method, it is called directly without enhancement
		else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
			// The target does not implement the hashCode() method itself.
			return hashCode();
		}
		else if (method.getDeclaringClass() == DecoratingProxy.class) {
			// There is only getDecoratedClass() declared -> dispatch to proxy config.
			return AopProxyUtils.ultimateTargetClass(this.advised);
		}
		// Methods defined in the Advised interface, or its parent, reflect calls directly and do not apply notifications
		else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
				method.getDeclaringClass().isAssignableFrom(Advised.class)) {
			// Service invocations on ProxyConfig with the proxy config...
			return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
		}

		Object retVal;

		// If the exposeProxy attribute is true, the proxy object is exposed
		ExposeProxy is one of the attributes of the @enableAspectJAutoProxy annotation
<2>		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			// Set the proxy object to AopContext
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		// Get the class of the target object
<3> target = targetSource.getTarget(); Class<? > targetClass = (target ! =null ? target.getClass() : null);

		// Get a list of Interceptor interceptors that can be applied to this method, sorted
<4>		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);


		// If there is no notification that can be applied to this method (Interceptor), this direct reflection calls method.invoke(target, args)
<5>		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
<6>		else {
			// Create the MethodInvocation to put the interceptor chain in
			invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// Execute interceptor chain
			retVal = invocation.proceed();
		}

		// Get the method return value type
<7> Class<? > returnType = method.getReturnType();if(retVal ! =null&& retVal == target && returnType ! = Object.class && returnType.isInstance(proxy) && ! RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// If the method returns this, return this; The proxy object proxy is assigned to retVal
			retVal = proxy;
		}
		// If the return type is an underlying type, such as int, long, etc., and if the return value is null, an exception is thrown
		else if (retVal == null&& returnType ! = Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException(
					"Null return value from advice does not match primitive return type for: " + method);
		}
		return retVal;
	}
	finally {
		if(target ! =null && !targetSource.isStatic()) {
			// Must have come from TargetSource.
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			// Restore old proxy.AopContext.setCurrentProxy(oldProxy); }}}Copy the code

The invoke() method takes the following steps:

  • < 1 > place, rightThe equals () and hashCode ()And so on
  • <2>, processingexposeProxyProperty, how the proxy is exposed
  • <3> gets the Class of the target object
  • <4> gets the chain of interceptors that can be applied to the current method
  • <5>, the interceptor chain is empty and the current method is called without enhancement
  • <6> executes the interceptor chain
  • <7>, get the return value type, and verify

We will focus on step <4> and step <6>, which are very important. Step <2> involves a lot. Finally, we will analyze the step <4> first.

Get the chain of interceptors that can be applied to the current method

Code:

//AdvisedSupport.java

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @NullableClass<? > targetClass) {
	MethodCacheKey cacheKey = new MethodCacheKey(method);
	// Fetch from the cache
	List<Object> cached = this.methodCache.get(cacheKey);
	if (cached == null) {
		// Get all interceptors
		cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
				this, method, targetClass);
		this.methodCache.put(cacheKey, cached);
	}
	return cached;
	}
Copy the code

Going further:

//DefaultAdvisorChainFactory.java

/** * Get the list of advisors from the provided config instance and iterate through them. If the Advisor is a IntroductionAdvisor, * determine if the Advisor can be applied to the targetClass targetClass. If it is PointcutAdvisor, determine whether * this Advisor can be applied to the target method. Convert a qualified Advisor to the Interceptor list through AdvisorAdaptor and return it. */
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
	Advised config, Method method, @NullableClass<? > targetClass) {

    List<Object> interceptorList = newArrayList<>(config.getAdvisors().length); Class<? > actualClass = (targetClass ! =null ? targetClass : method.getDeclaringClass());
    // check to see if the cloud IntroductionAdvisor is included
    boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
    // Here you actually register a series of AdvisorAdapters to convert the advice Advisor into a MethodInterceptor
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    / / traverse
    for (Advisor advisor : config.getAdvisors()) {
    	if (advisor instanceof PointcutAdvisor) {
    		PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
    		// Match the current Bean type with ClassFilter
    		if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
    			// Convert the notification Advisor into an Interceptor
    			MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
    			MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
    			// Check whether the pointcut of the current advisor matches the current method
    			if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
    				if (mm.isRuntime()) {
    					// Creating a new object instance in the getInterceptors() method
    					// isn't a problem as we normally cache created chains.
    					for (MethodInterceptor interceptor : interceptors) {
    						// Join the interceptor chain
    						interceptorList.add(newInterceptorAndDynamicMethodMatcher(interceptor, mm)); }}else {
    					// Join the interceptor chaininterceptorList.addAll(Arrays.asList(interceptors)); }}}}else if (advisor instanceof IntroductionAdvisor) {
    		IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
    		// Notifier of the type IntroductionAdvisor, only class level matching
    		if(config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) { Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); }}else{ Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); }}return interceptorList;
}
Copy the code

The main logic here is not very complicated:

  • Iterate over all notifiers
  • For a notifier of type PointcutAdvisor, the class and method are matched by calling the Pointcut held by the notifier, and a successful match indicates that the notification logic should be woven into the current method
  • Convert the notification Advisor into an Interceptor
  • Returns the interceptor number chain

The chain of interceptors returned here is ordered, in order of afterReturn, after, around, and before (sortAdvisors(eligibleAdvisors) when getting all notifications), for later execution.

1.2. Execute interceptor chain

Now that we have the interceptor chain, let’s see how it works:

//ReflectiveMethodInvocation.java

// The current interceptor subscript
private int currentInterceptorIndex = -1;
// Interceptor chain set
protected finalList<? > interceptorsAndDynamicMethodMatchers;public Object proceed(a) throws Throwable {
	//	We start with an index of -1 and increment early.
	// The last interceptor in the chain completes execution
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		// Execute the target method
		return invokeJoinpoint();
	}

	// Each time a new interceptor is executed, subscript +1
	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

	// If you want to dynamically match pointcuts
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		InterceptorAndDynamicMethodMatcher dm =
				(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
		// If it is a dynamic pointcut, parameters need to be matched to determine whether the dynamic crosscutting logic needs to be executed
		if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
		else {
			// Fail to perform an interception for the next Interceptor
			returnproceed(); }}else {
		// Perform the current interception
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); }}Copy the code

Proceed executes the interceptor chain in reverse order according to currentInterceptorIndex, +1 each time, and the target method when all interceptors are complete. Here we can raise a few questions:

  • When a method matches multiple notifications, in what order are the different notifications executed?
  • The target method is executed only after all notifications have been executed.

We look down with these two questions,

The invoke(this) method in PROCEED has different implementation classes, depending on the type of advice, such as:

  • The @ Before the corresponding MethodBeforeAdviceInterceptor

  • @ After corresponding AspectJAfterAdvice

  • @ AfterReturning corresponding AfterReturningAdviceInterceptor

  • @ AfterThrowing corresponding AspectJAfterThrowingAdvice

  • Corresponding AspectJAroundAdvice @ Around

1.2.1. Post notification

Since the interceptor chain is in reverse order, let’s first look at the code for post-notification:

//AspectJAfterAdvice.java

public class AspectJAfterAdvice extends AbstractAspectJAdvice
		implements MethodInterceptor.AfterAdvice.Serializable {

	public AspectJAfterAdvice( Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}


	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			// Execute the next interceptor chain
			return mi.proceed();
		}
		finally {
			// execute the post-logic
			invokeAdviceMethod(getJoinPointMatch(), null.null); }}@Override
	public boolean isBeforeAdvice(a) {
		return false;
	}

	@Override
	public boolean isAfterAdvice(a) {
		return true; }}Copy the code

As you can see, the post-interceptor will proceed to the next interceptor first, and when the interceptor chain is finished, proceed() executes the target method, and then the post-logic.

1.2.2 Surround notification

Let’s take a look at the surround interceptor:

//AspectJAroundAdvice.java

public Object invoke(MethodInvocation mi) throws Throwable {
	if(! (miinstanceof ProxyMethodInvocation)) {
		throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
	}
	ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
	ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
	JoinPointMatch jpm = getJoinPointMatch(pmi);
	// Execute the wrap logic
	return invokeAdviceMethod(pjp, jpm, null.null);
	}
Copy the code

The wrap interceptor executes the wrap logic directly, and since we configured the following code in LogAspect earlier:

@Aspect
@Component
@EnableAspectJAutoProxy
public class LogAspect {

	@Pointcut("execution(* com.mydemo.work.StudentController.getName(..) )"
	private void log(a){}


	@Before("log()")
	public void doBefore(a) {
		System.out.println("===before");
	}

	@After("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAfter(a) {
		System.out.println("===after");
	}

	@AfterReturning("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAfterReturn(a) {
		System.out.println("===afterReturn");
	}

	@Around("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAround(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("===around before");
		pjp.proceed();
		System.out.println("===around after"); }}Copy the code

So the interceptor executes ========around before, and then the next interceptor.

1.2.3 Pre-notification

Let’s take a look at the front interceptor:

//MethodBeforeAdviceInterceptor.java

public class MethodBeforeAdviceInterceptor implements MethodInterceptor.Serializable {

	private MethodBeforeAdvice advice;

	/**
	 * Create a new MethodBeforeAdviceInterceptor for the given advice.
	 * @param advice the MethodBeforeAdvice to wrap
	 */
	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// Execute the pre-logic
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
		// Execute the next interceptor
		returnmi.proceed(); }}Copy the code

As you can see, the pre-interceptor, after executing the pre-method, calls MethodInvocation#proceed() to proceed to the next interceptor.

Ii. Order of execution of the notice

2.1. Under the same Aspect

So if you’re going to get a little convoluted here, let’s go through the order in which the notifications are executed, which is the order in which the interceptors are executed. First, the interceptor chain is sorted in reverse order, as follows:

You can see that the notifications are sorted by afterReturn, after, around, and before.

Draw the execution flow chart:

Run it again with the unit testLogAspectAnd the printed result is as follows:

===around before
===before
do getName
===around after
===after
===afterReturn
Copy the code

Just as we suspected. Now we can answer the previous question about the order in which a method matches multiple notifications.

2.2. Under multiple aspects

In practice, however, it is often possible for a method to be intercepted by more than one Aspect; for example, we want the business method to be intercepted first by the logging Aspect and then by the exception Aspect.

The Spring framework has figured this out for us. If we have multiple aspects, they will actually be executed randomly, in no clear order. But Spring provides an @Order annotation that lets us specify the Order in which the Aspect is executed. Let’s add a new exception handling Aspect:

@Aspect
@Component
@EnableAspectJAutoProxy
@Order(2)
public class ErrorAspect {

	@Pointcut("execution(* com.mydemo.work.StudentController.getName(..) )"
	private void log(a){}


	@Before("log()")
	public void doBeforeError(a) {
		System.out.println("=== error before");
	}

	@After("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAfterError(a) {
		System.out.println("=== error after");
	}

	@AfterReturning("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAfterReturnError(a) {
		System.out.println("=== error afterReturn");
	}

	@Around("execution(* com.mydemo.work.StudentController.getName(..) )"
	public void doAroundError(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("=== error around before");
		pjp.proceed();
		System.out.println("=== error around after"); }}Copy the code

Also annotate LogAspect @Order(1), which means that the notification for LogAspect is executed before the ErrorAspect. Let’s take a look at the results of the unit quiz:

===around before
===before
=== error around before
=== error before
do getName
=== error around after
=== error after
=== error afterReturn
===around after
===after
===afterReturn
Copy the code

You can see that LogAspect’s notification does get executed first. Why is that? Let’s take a look at the debug source code, mainly look at the interceptor chain, as shown in the figure:

As you can see, the interceptor chain is already arranged in order, so the code can be executed in the order of the interceptor chain to ensure that the two sections are intercepted in order. So why does it sort this way? Let’s draw a picture:

As shown in the figure above, the two aspects are intercepted like two circles on the outside, with the target method in the middle. When a request comes in to execute the target method:

  • It’s going to be circled first@Order(1)Interceptor interception
  • And then the inner circle@Order(2)Interceptor interception
  • After executing the target method, pass first@Order(2)The rear interceptor
  • Final pass@Order(1)The rear interceptor

Here we notice the execution of the order on the analysis of the end, which is still to go from the source level to understand, not rote memorization, so as to remember firm.

Third, exposeProxy

When we analyzed the invoke() method above, we had this code:

        // If the exposeProxy attribute is true, the proxy object is exposed
	ExposeProxy is one of the attributes of the @enableAspectJAutoProxy annotation
	if (this.advised.exposeProxy) {
		// Make invocation available if necessary.
		// Set the proxy object to AopContext
		oldProxy = AopContext.setCurrentProxy(proxy);
		setProxyContext = true;
	}
Copy the code

In this section we will examine in detail what is meant here. First exposeProxy is a property in the @enableAspectJAutoProxy annotation. It can be set to true or false, and the default is false. This property is used to solve the problem that when the target method calls other methods in the same object, the aspect logic of the other methods cannot be executed. What does that mean? Let’s take an example:

public class Student implements Person {

    @Override
    public void haveFun(a) {
        System.out.println("Basketball");
        this.hello("Football");
    }

    @Override
    public void hello(String action) {
        System.out.println("hello "+ action); }}Copy the code

In the same student.class, a haveFun() method calls another Hello (String Action) method of this class, and the aspect logic on Hello (String Action) cannot be executed.

3.1. Why is this class call invalid?

So why is that?

We know that the essence of AOP is to generate a proxy object for the target object, enhance the original method, and then execute the methods in our proxy class.

The haveFun() method uses this.hello(” football “), where this is not a proxy object, but a primitive object, so calling Hello (String Action) from the primitive object is not enhanced, so the section will not work.

So the question is, why is this not a proxy object, but a primitive object?

3.2. Why is this not a proxy object

One of the above code ReflectiveMethodInvocation# proceed () method is as follows:

public Object proceed(a) throws Throwable {
	//	We start with an index of -1 and increment early.
	// The last interceptor in the chain completes execution
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		// Execute the target method
		return invokeJoinpoint();
	}

	/ /... omit
		if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
        / /... omit
	}
	else {
		// Perform the current interception
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); }}Copy the code

After all interceptors are executed, the target method is executed, which we trace to invokeJoinpoint().

//ReflectiveMethodInvocation.java

protected Object invokeJoinpoint(a) throws Throwable {
        / / focus on this target
	return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
	}
Copy the code
//ReflectiveMethodInvocation.java

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
		throws Throwable {

	// Use reflection to invoke the method.
	try {
		ReflectionUtils.makeAccessible(method);
		/ / key target
		return method.invoke(target, args);
	}
    / /... omit
	}
Copy the code

As you can see, the original target object is called to execute our target method, so this of this.hello(” football “) in haveFun() is actually the original target object.

3.3 how does exposeProxy work?

So the exposeProxy attribute is designed to solve this problem, so how does it work? Let’s go back to the code:

     // If the exposeProxy attribute is true, the proxy object is exposed
	ExposeProxy is one of the attributes of the @enableAspectJAutoProxy annotation
	if (this.advised.exposeProxy) {
		// Make invocation available if necessary.
		// Set the proxy object to AopContext
		oldProxy = AopContext.setCurrentProxy(proxy);
		setProxyContext = true;
	}
Copy the code

Continue tracking:

//AopContext.java
// Store the proxy object ThreadLocal
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");

static Object setCurrentProxy(@Nullable Object proxy) {
	Object old = currentProxy.get();
	if(proxy ! =null) {
	        // Store proxy objects to ThreadLocal
		currentProxy.set(proxy);
	}
	else {
		currentProxy.remove();
	}
	return old;
}
Copy the code

If exposeProxy is set to true, the proxy object is stored in ThreadLocal, and when called in this class, the proxy class is retrieved from ThreadLocal to call the target method instead of this, which fixes the problem.

Finally, consider the following case:

public class Student implements Person {

    @Override
    @Transactional
    public void haveFun(a) {
        System.out.println("Basketball");
        this.hello("Football");
    }

    @Override
    @Transactional
    public void haveFun(String action) {
        System.out.println("haveFun "+ action); }}Copy the code

In this case, the haveFun(String Action) transaction will not take effect for the reason we just analyzed. In fact, @Transactional is also implemented by AOP at heart, so this problem can arise as well. The solution is to set exposeProxy=true to the @EnableAspectJAutoProxy annotation

conclusionCopy the code

Sequence diagram:

Here SpringAOP source code analysis will come to an end, due to my experience and technical level is limited, so can only first understand so much, if there are mistakes, welcome to put forward opinions.

Reference: www.tianxiaobo.com/2018/06/22/…