springboot aop加载流程
一、創建幾個切面。
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);@Autowiredprivate RedisService redisService;@Pointcut("execution(* com.tpw.newday.service..*.*(..)))")private void cacheMethod() {}/*** 前置通知:在目標方法執行前調用*/@Before("cacheMethod()")public void begin() {logger.info("==@Before== lipy cache method : begin");}/*** 后置通知:在目標方法執行后調用,若目標方法出現異常,則不執行*/@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));}/*** 后置/最終通知:無論目標方法在執行過程中出現一場都會在它之后調用*/@After("cacheMethod()")public void after() {logger.info("==@After== lipy cache method : finally returning");}/*** 異常通知:目標方法拋出異常時執行*/@AfterThrowing(value = "cacheMethod()",throwing = "ex")public void afterThrowing(Throwable ex) {logger.info("==@AfterThrowing== lipy cache method : after throwing ex:" + ex.getMessage());}/*** 環繞通知:靈活自由的在目標方法中切入代碼*/@Around("cacheMethod()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 獲取目標方法的名稱String methodName = joinPoint.getSignature().getName();// 獲取方法傳入參數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;}/*** 獲取redis的key*/private String parseKey(String fieldKey, Method method, Object[] args) {//獲取被攔截方法參數名列表(使用Spring支持類庫)LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();String[] paraNameArr = u.getParameterNames(method);//使用SPEL進行key的解析ExpressionParser parser = new SpelExpressionParser();//SPEL上下文StandardEvaluationContext context = new StandardEvaluationContext();//把方法參數放入SPEL上下文中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;}/*** 獲取方法中聲明的注解** @param joinPoint* @return* @throws NoSuchMethodException*/public Object handleMethod(ProceedingJoinPoint joinPoint) throws Throwable {// 獲取方法名String methodName = joinPoint.getSignature().getName();// 反射獲取目標類Class<?> targetClass = joinPoint.getTarget().getClass();// 拿到方法對應的參數類型Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();// 根據類、方法、參數類型(重載)獲取到方法的具體信息Method objMethod = targetClass.getMethod(methodName, parameterTypes);// 獲取方法傳入參數Object[] params = joinPoint.getArgs();// 拿到方法定義的注解信息CacheProcesser annotation = objMethod.getDeclaredAnnotation(CacheProcesser.class);if (ObjectUtil.isNull(annotation)){// 執行源方法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();} }二、生成代理對象流程
? 1.在application.refresh方法中實例化代理對象前,會有一個擴展點
搜索容器中所有的InstantiationAwareBeanPostProcessor類,進行對象替換。2.在@EnableAspectJAutoProxy(proxyTargetClass = true)中會注冊AnnotationAwareAspectJAutoProxyCreator生成動態代理切面攔截類
?可以看到這個類實現了InstantiationAwareBeanPostProcessor類,所以會被觸發。
3.AbstractAutoProxyCreator.postProcessBeforeInstantiation中的shouldSkip方法,會搜索當前系統所有的aspect注冊類。
?可以看到搜索到了所有系統的aspect注解類的BEAN名字,并且每個@Before,@After注解方法都變成了一個adviser(由pointCut和advice)包含。
?4.在具體的對象初始化后,會觸發AbstractAutoProxyCreator.postProcessAfterInitialization,在這里根據代理類是否能滿足當前系統中所有切面的pointcut,如果滿足,則生成AOP的責任鏈代理類(責任鏈為所有的adviser封裝成methodInterrput攔截器列表再加上jointPoint的實際調用方法)。
目前只有UserService類滿足切面的要求,我們再具體看下切面的列表。?
5.接著我們看到使用了cglib代理類,并配置了攔截回調對象。?
?
6.最終生成的代理對象如下
?生成代理類的方法
CglibAopProxy @Overridepublic 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 aboveenhancer.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() failedthrow new AopConfigException("Unexpected AOP exception", ex);}}生成CGLIB的CALLBACK回調數組,其實就是方法攔截器的列表。
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 advicetargetInterceptor, // invoke target without considering advice, if optimizednew SerializableNoOp(), // no override for methods mapped to thistargetDispatcher, 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;}三、查看調用流程
1.可以看到先調第一個方法攔截器,DynamicAdvisedInterceptor.interrupt
我們再看下DynamicAdvisedInterceptor中生成的責任鏈
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;}@Override@Nullablepublic 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);}}}?這里會封裝一個
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();CglibMethodInvocation繼承于ReflectiveMethodInvocation private static class CglibMethodInvocation extends ReflectiveMethodInvocation {接著來看ReflectiveMethodInvocation.proceed()
可以看到他會依次遞歸調用所有責任鏈上的攔截器,如果都處理完了,再調用真實的代理方法。
@Override@Nullablepublic 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);}}@Nullableprotected Object invokeJoinpoint() throws Throwable {return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);}?我們看下CglibMethodInvocation的攔截器調用順序
可看到,先調用aroud->before->after->afterreturning->afterthrowing
aroud
@Around("cacheMethod()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 獲取目標方法的名稱String methodName = joinPoint.getSignature().getName();// 獲取方法傳入參數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)){// 執行源方法return joinPoint.proceed(); }return result; } MethodInvocationProceedingJoinPoint@Overridepublic 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;} }這個adviser,因為他是最后一個攔截器,調用了真實代理方法。
?
調用完后,再不斷返回退出堆棧,按調用方向,反向返回。
因為沒有throwing,所以after returning->after
如果有異常,AfterThrowing->after
因為afterThrowing會繼續拋出異常,而after returning沒有捕獲異常,所以不會處理。
?
這時會直接到Aftering的finally?
?After執行完,再回到aroud的結束通知,但是前提是aroud要對jointpoint.procesed()捕獲異常。
?
總結
以上是生活随笔為你收集整理的springboot aop加载流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springcloud feign 加上
- 下一篇: WINCE串口通讯经验小结