springboot aop loading process

Posted by Mordred on Fri, 25 Feb 2022 13:48:14 +0100

1, Create several sections.

package com.tpw.newday.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.tpw.newday.annation.CacheProcesser;
import com.tpw.newday.common.MyConstants;
import com.tpw.newday.redis.RedisParam;
import com.tpw.newday.redis.RedisService;
import com.tpw.newday.utils.RedisUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.validation.constraints.Null;
import java.lang.reflect.Method;

/**
 * <h3>newday</h3>
 * <p>xx</p>
 *
 * @author : lipengyao
 * @date : 2021-04-30 15:56:19
 **/
@Aspect
@Component
@Order(2)
public class CacheAspect {

    private static final Log logger = LogFactory.getLog(CacheAspect.class);

    private RedisUtil redisUtil = new RedisUtil(MyConstants.redis_host_ip,MyConstants.redis_port,MyConstants.redis_password);

    @Autowired
    private RedisService redisService;

    @Pointcut("execution(* com.tpw.newday.service..*.*(..)))")
    private void cacheMethod() {

    }

    /**
     * Pre notification: call before execution of target method
     */
    @Before("cacheMethod()")
    public void begin() {
        logger.info("==@Before== lipy cache method : begin");
    }

    /**
     * Post notification: after the execution of the target method, if the target method has an exception, it will not execute.
     */
    @AfterReturning(value = "cacheMethod()",returning = "ret")
    public void afterReturning(JoinPoint jp,Object ret) {
        Object[] args = jp.getArgs();
        logger.info("==@AfterReturning== lipy cache method : after returning ret:" + JSONUtil.toJsonStr(ret)
        +" args:" + JSONUtil.toJsonStr(args));
    }

    /**
     * Post / final notification: whenever a target method appears during execution, it will be called after it.
     */
    @After("cacheMethod()")
    public void after() {
        logger.info("==@After== lipy cache method : finally returning");
    }

    /**
     * Exception notification: executed when the target method throws an exception
     */
    @AfterThrowing(value = "cacheMethod()",throwing = "ex")
    public void afterThrowing(Throwable ex) {
        logger.info("==@AfterThrowing==  lipy cache method : after throwing ex:" + ex.getMessage());
    }

    /**
     * Surround notification: flexible and free to cut into code in the target method
     */
    @Around("cacheMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // Gets the name of the target method
        String methodName = joinPoint.getSignature().getName();
        // Get method passed in parameters
        Object[] params = joinPoint.getArgs();
        logger.info("==@Around== lipy cache method -->begin method name " + methodName + " args " + (params.length > 0 ? params[0] :
        null));
        Object result  =  handleMethod(joinPoint);
        logger.info("==@Around== lipy cache method -->end method name " + methodName + " result " + JSONUtil.toJsonStr(result));
        return  result;
    }

    /**
     * Get the key of redis
     */
    private String parseKey(String fieldKey, Method method, Object[] args) {
        //Get the parameter name list of the intercepted method (using the Spring support class library)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //Use SPEL to parse key s
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL context
        StandardEvaluationContext context = new StandardEvaluationContext();
        //Put method parameters into the SPEL context
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        String key= parser.parseExpression(fieldKey).getValue(context, String.class);
        return  key;
    }

    /**
     * Gets the annotation declared in the method
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public Object handleMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // Get method name
        String methodName = joinPoint.getSignature().getName();
        // Reflection get target class
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // Get the parameter type corresponding to the method
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // Get the specific information of the method according to the class, method and parameter type (overload)
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);

        // Get method passed in parameters
        Object[] params = joinPoint.getArgs();
        // Get the annotation information of the method definition
        CacheProcesser annotation = objMethod.getDeclaredAnnotation(CacheProcesser.class);
        if (ObjectUtil.isNull(annotation)){
            // Execution source method
            return joinPoint.proceed();
        }

        if (annotation.cacheOperation() == CacheProcesser.CacheOperation.QUERY){
            String cacheKey = annotation.key()+ ":"+this.parseKey(annotation.fieldKey(),objMethod ,params );
            RedisParam redisParam = new RedisParam(cacheKey,annotation.expireTime());
            Object cacheResult = redisService.get(redisParam);
            if (ObjectUtil.isNotNull(cacheResult)){
                logger.info(" get from cache key:" + cacheKey);
                return cacheResult;
            }else{
                Object obj = joinPoint.proceed();
                redisService.set(redisParam,obj );
                logger.info(" call method,set to  cache key:" + cacheKey);
                return  obj;
            }
        }else if (annotation.cacheOperation() == CacheProcesser.CacheOperation.UPDATE ||
                annotation.cacheOperation() == CacheProcesser.CacheOperation.DELETE ){
            Object obj = joinPoint.proceed();
            String cacheKey = annotation.key()+ ":"+this.parseKey(annotation.fieldKey(),objMethod ,params );
            RedisParam redisParam = new RedisParam(cacheKey,annotation.expireTime());
            logger.info(" delete from cache key:" + cacheKey);
            redisService.remove(redisParam);
            return  obj;
        }
        return  joinPoint.proceed();
    }
}

2, Generate proxy object process

  1. In application There will be an extension point before instantiating the proxy object in the refresh method

Search all instantiaawarebeanpostprocessor classes in the container for object replacement.

2. In @ EnableAspectJAutoProxy(proxyTargetClass = true), the AnnotationAwareAspectJAutoProxyCreator will be registered to generate a dynamic proxy aspect interception class

You can see that this class implements the instantiawarebeanpostprocessor class, so it will be triggered.

3.AbstractAutoProxyCreator. The shouldSkip method in postprocessbeforeinstance will search all aspect registration classes in the current system.

 

You can see that BEAN names of aspect annotation classes of all systems have been searched, and each @ Before,@After annotation method has become an advisor (including pointCut and advice).

 4. Abstractautoproxycreator will be triggered after the specific object is initialized Postprocessafterinitialization. Here, according to whether the proxy class can meet the pointcut s of all aspects in the current system, if so, the responsibility chain proxy class of AOP will be generated (the responsibility chain is encapsulated into the methodInterrput interceptor list for all advisors, plus the actual calling method of jointPoint).

At present, only UserService class meets the requirements of aspects. Let's see the list of aspects.  

5. Next, we see that the cglib proxy class is used and the interception callback object is configured.  

 

6. The final generated proxy objects are as follows

 

Method of generating proxy class

CglibAopProxy
@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
		}

		try {
			Class<?> rootClass = this.advised.getTargetClass();
			Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

			Class<?> proxySuperClass = rootClass;
			if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
				proxySuperClass = rootClass.getSuperclass();
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
					this.advised.addInterface(additionalInterface);
				}
			}

			// Validate the class, writing log messages as necessary.
			validateClassIfNecessary(proxySuperClass, classLoader);

			// Configure CGLIB Enhancer...
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
			return createProxyClassAndInstance(enhancer, callbacks);
		}
		catch (CodeGenerationException | IllegalArgumentException ex) {
			throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
					": Common causes of this problem include using a final class or a non-visible class",
					ex);
		}
		catch (Throwable ex) {
			// TargetSource.getTarget() failed
			throw new AopConfigException("Unexpected AOP exception", ex);
		}
	}

The CALLBACK array generated by CGLIB is actually a list of method interceptors.

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
		// Parameters used for optimization choices...
		boolean exposeProxy = this.advised.isExposeProxy();
		boolean isFrozen = this.advised.isFrozen();
		boolean isStatic = this.advised.getTargetSource().isStatic();

		// Choose an "aop" interceptor (used for AOP calls).
		Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

		// Choose a "straight to target" interceptor. (used for calls that are
		// unadvised but can return this). May be required to expose the proxy.
		Callback targetInterceptor;
		if (exposeProxy) {
			targetInterceptor = (isStatic ?
					new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
					new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
		}
		else {
			targetInterceptor = (isStatic ?
					new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
					new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
		}

		// Choose a "direct to target" dispatcher (used for
		// unadvised calls to static targets that cannot return this).
		Callback targetDispatcher = (isStatic ?
				new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

		Callback[] mainCallbacks = new Callback[] {
				aopInterceptor,  // for normal advice
				targetInterceptor,  // invoke target without considering advice, if optimized
				new SerializableNoOp(),  // no override for methods mapped to this
				targetDispatcher, this.advisedDispatcher,
				new EqualsInterceptor(this.advised),
				new HashCodeInterceptor(this.advised)
		};

		Callback[] callbacks;

		// If the target is a static one and the advice chain is frozen,
		// then we can make some optimizations by sending the AOP calls
		// direct to the target using the fixed chain for that method.
		if (isStatic && isFrozen) {
			Method[] methods = rootClass.getMethods();
			Callback[] fixedCallbacks = new Callback[methods.length];
			this.fixedInterceptorMap = new HashMap<>(methods.length);

			// TODO: small memory optimization here (can skip creation for methods with no advice)
			for (int x = 0; x < methods.length; x++) {
				Method method = methods[x];
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
				fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
						chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
				this.fixedInterceptorMap.put(method, x);
			}

			// Now copy both the callbacks from mainCallbacks
			// and fixedCallbacks into the callbacks array.
			callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
			System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
			System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
			this.fixedInterceptorOffset = mainCallbacks.length;
		}
		else {
			callbacks = mainCallbacks;
		}
		return callbacks;
	}

3, View call flow

1. You can see that the first method interceptor, dynamic advised interceptor, is called first interrupt

 

Let's look at the responsibility chain generated in dynamic advised interceptor

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

		private final AdvisedSupport advised;

		public DynamicAdvisedInterceptor(AdvisedSupport advised) {
			this.advised = advised;
		}

		@Override
		@Nullable
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					// Make invocation available if necessary.
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// Check whether we only have one InvokerInterceptor: that is,
				// no real advice, but just reflective invocation of the target.
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					// 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 = methodProxy.invoke(target, argsToUse);
				}
				else {
					// We need to create a method invocation...
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null && !targetSource.isStatic()) {
					targetSource.releaseTarget(target);
				}
				if (setProxyContext) {
					// Restore old proxy.
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

One will be encapsulated here

retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

CglibMethodInvocation inherits from ReflectiveMethodInvocation
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

Next, let's look at reflective method invocation proceed()

You can see that he will recursively call all interceptors on the responsibility chain in turn. If they are all processed, then call the real proxy method.

@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

	@Nullable
	protected Object invokeJoinpoint() throws Throwable {
		return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
	}

Call our method invocation interceptor

 

As you can see, first call around - > before - > after - > after returning - > after throwing

aroud

@Around("cacheMethod()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    //Gets the name of the target method
    String methodName = joinPoint.getSignature().getName();
    //Get method passed in parameters
    Object[] params = joinPoint.getArgs();
    logger.info("==@Around== lipy cache method -->begin method name " + methodName + " args " + (params.length > 0 ? params[0] :
    null));
if (ObjectUtil.isNull(annotation)){
    //Execution source method
    return joinPoint.proceed();
}
    return  result;
}

MethodInvocationProceedingJoinPoint
	@Override
	public Object proceed() throws Throwable {
		return this.methodInvocation.invocableClone().proceed();
	}

 

before adviser

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
   return mi.proceed();
}

after adviser

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   try {
      return mi.proceed();
   }
   finally {
      invokeAdviceMethod(getJoinPointMatch(), null, null);
   }
}

after returning adviser

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   Object retVal = mi.proceed();
   this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
   return retVal;
}

after throwing adviser

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   try {
      return mi.proceed();
   }
   catch (Throwable ex) {
      if (shouldInvokeOnThrowing(ex)) {
         invokeAdviceMethod(getJoinPointMatch(), null, ex);
      }
      throw ex;
   }
}

This advisor, because it is the last interceptor, calls the real proxy method.

 

 

After the call, continue to return and exit the stack, and return in the reverse direction according to the call direction.

After returning - > after because there is no throwing

If there is an exception, afterthrowing - > after

Topics: Java Apache Spring Boot