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, right
The equals () and hashCode ()
And so on - <2>, processing
exposeProxy
Property, 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 testLogAspect
And 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/…