为了账号安全,请及时绑定邮箱和手机立即绑定

Spring基础系列-AOP源码分析 (二)

标签:
Java

 然后返回到源码9中,我们开始生成代理:

  源码17-来自:AbstractAutoProxyCreator

复制代码

 1     protected Object createProxy(Class<?> beanClass, @Nullable String beanName, 2             @Nullable Object[] specificInterceptors, TargetSource targetSource) { 3  4         if (this.beanFactory instanceof ConfigurableListableBeanFactory) { 5             //暴露目标类,将其保存到BeanDefinition中 6             AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); 7         } 8  9         //创建一个新的代理工厂,并为其拷贝当前类中的相关配置属性10         ProxyFactory proxyFactory = new ProxyFactory();11         proxyFactory.copyFrom(this);12 13         if (!proxyFactory.isProxyTargetClass()) {14             //校验proxyTargetClass设置,如果设置不是直接代理目标类,则采用默认的JDK动态代理指定接口15             if (shouldProxyTargetClass(beanClass, beanName)) {16                 //校验该Bean的BeanDefinition中的preserveTargetClass属性,是否被代理工厂设置为true,如果设置为true,则表示代理工厂希望代理类可以强转为目标类17                 proxyFactory.setProxyTargetClass(true);18             }19             else {20                 //否则表示基于接口创建代理21                 evaluateProxyInterfaces(beanClass, proxyFactory);22             }23         }24 25         //将拦截器封装成通知26         Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);27         proxyFactory.addAdvisors(advisors);//加入增强器28         proxyFactory.setTargetSource(targetSource);//设置要代理的类29         customizeProxyFactory(proxyFactory);//子类定制代理30         //用于控制代理工厂被配置之后,是否还允许修改通知,默认为false(表示不允许修改)31         proxyFactory.setFrozen(this.freezeProxy);32         if (advisorsPreFiltered()) {33             proxyFactory.setPreFiltered(true);34         }35         //创建代理36         return proxyFactory.getProxy(getProxyClassLoader());37     }

复制代码

  源码中执行了一大串的工作,都在为最后的创建代理做准备:

   源码18-来自:ProxyFactory

1     public Object getProxy(@Nullable ClassLoader classLoader) {2         //创建AOP代理,并获取代理对象3         return createAopProxy().getProxy(classLoader);4     }

  源码19-来自:ProxyCreatorSupport

复制代码

 1     protected final synchronized AopProxy createAopProxy() { 2         if (!this.active) { 3             activate();//激活开关 4         } 5         // 获取AOP代理工厂,使用AOP代理工厂创建AOP代理 6         return getAopProxyFactory().createAopProxy(this); 7     } 8  9     private void activate() {10         this.active = true;11         // 回调监听器的activated方法12         for (AdvisedSupportListener listener : this.listeners) {13             listener.activated(this);14         }15     }16 17     public AopProxyFactory getAopProxyFactory() {18         return this.aopProxyFactory;19     }

复制代码

  重点在createAopProxy方法:

  源码20-来自:DefaultAopProxyFactory

复制代码

 1     @Override 2     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 3         if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { 4             // 如果代理需要执行优化或者proxyTargetClass=true或者不存在代理接口 5             Class<?> targetClass = config.getTargetClass(); 6             if (targetClass == null) { 7                 throw new AopConfigException("TargetSource cannot determine target class: " + 8                         "Either an interface or a target is required for proxy creation."); 9             }10             if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {11                 //如果目标类是接口或者是动态生成的代理类,则使用JDK动态代理12                 return new JdkDynamicAopProxy(config);13             }14             //创建CGLIB动态AOP代理对象15             return new ObjenesisCglibAopProxy(config);16         }17         else {18             //创建JDK动态AOP代理对象19             return new JdkDynamicAopProxy(config);20         }21     }

复制代码

  至此,代理对象生成,至于是使用JDK动态代理,还是Cglib动态代理,机理如下:

    如果目标类实现了接口,默认使用JDK动态代理

    如果目标类实现了接口,可以强制使用Cglib动态代理

    如果目标没有实现接口,必须采用Cglib动态代理

  至于如何强制使用Cglib动态代理:

    首先需要添加CGLIB库,然后设置proxyTargetClass置为true,进行强制使用基于类的CGLIB动态代理。

  JDK动态代理和Cglib动态代理的区别:

    JDK动态代理只能基于接口生成代理,方式是通过实现JDK提供的InvocationHandler接口中的invoke方法来实现针对目标类指定方法的代理调用。

    CGLIB可以基于类生成代理,方式是通过对目标类生成一个子类,覆盖其中的方法。

   返回到源码18中,创建了AOP代理之后,执行其getProxy方法:(我们看下JDK动态代理的实现)

  源码21-来自:JdkDynamicAopProxy

复制代码

 1     @Override 2     public Object getProxy(@Nullable ClassLoader classLoader) { 3         if (logger.isDebugEnabled()) { 4             logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 5         } 6         // 1-获取用于代理的全部接口的集合(数组) 7         Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); 8         // 2-查找接口集合中可能定义的equals方法获取hashCode方法 9         findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);10         // 3-创建指定接口的代理实例11         return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);12     }

复制代码

  上面的代理主要做了三件事情:

    1-首先获取用于代理的全部接口集;

    2-然后查找该接口集中有无定义equals和hashCode方法;

    3-最后执行代理实例的创建。

  首先看下第一件事情:completeProxiedInterfaces

  源码22-来自:AopProxyUtils

复制代码

 1     // 获取用于代理的接口的集合 2     static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { 3         // 获取配置中所有的接口 4         Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); 5         if (specifiedInterfaces.length == 0) { 6             // No user-specified interfaces: check whether target class is an interface. 7             Class<?> targetClass = advised.getTargetClass(); 8             if (targetClass != null) { 9                 if (targetClass.isInterface()) {10                     advised.setInterfaces(targetClass);11                 }12                 else if (Proxy.isProxyClass(targetClass)) {13                     advised.setInterfaces(targetClass.getInterfaces());14                 }15                 specifiedInterfaces = advised.getProxiedInterfaces();16             }17         }18         // 将SpringProxy、Advised、DecoratingProxy三个接口添加到接口集中19         boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);20         boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);21         boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));22         int nonUserIfcCount = 0;23         if (addSpringProxy) {24             nonUserIfcCount++;25         }26         if (addAdvised) {27             nonUserIfcCount++;28         }29         if (addDecoratingProxy) {30             nonUserIfcCount++;31         }32         Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];33         System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);34         int index = specifiedInterfaces.length;35         if (addSpringProxy) {36             proxiedInterfaces[index] = SpringProxy.class;37             index++;38         }39         if (addAdvised) {40             proxiedInterfaces[index] = Advised.class;41             index++;42         }43         if (addDecoratingProxy) {44             proxiedInterfaces[index] = DecoratingProxy.class;45         }46         return proxiedInterfaces;47     }

复制代码

  下面我们再看看之前的第二步:findDefinedEqualsAndHashCodeMethods

  源码23-来自:JdkDynamicAopProxy

复制代码

 1     private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) { 2         for (Class<?> proxiedInterface : proxiedInterfaces) { 3             Method[] methods = proxiedInterface.getDeclaredMethods(); 4             for (Method method : methods) { 5                 // 遍历每一个接口,再获取其中的方法进行遍历,逐个校验其是否是equals方法,或者hashCode方法,只有当二者都被定义之后校验才会结束,否则一直进行下去 6                 if (AopUtils.isEqualsMethod(method)) { 7                     this.equalsDefined = true; 8                 } 9                 if (AopUtils.isHashCodeMethod(method)) {10                     this.hashCodeDefined = true;11                 }12                 if (this.equalsDefined && this.hashCodeDefined) {13                     return;14                 }15             }16         }17     }

复制代码

  最后就是我们的重点步骤:Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),这个操作大家都不会陌生,这是JDK动态代理创建代理类的通用方式。这个方法的参数列表中最后一个是一个大家都很熟悉的家伙:InvocationHandler,我们都知道JDK动态代理的执行逻辑都是在一个实现了InvocationHandler接口中的invoke方法中,这里传入this,表示当前实例,代表当前实例所属类应该实现了InvocationHandler接口,之前已经成功创建了JDK动态代理对象,那么当我们发生对指定目标方法的调用时,就会触发JdkDynamicAopProxy中的invoke方法:

  源码24-来自:JdkDynamicAopProxy

1 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {2     ...3 }

  这也就是说,这个类中必然实现了invoke方法:

  源码25-来自:JdkDynamicAopProxy

复制代码

 1     @Override 2     @Nullable 3     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4         MethodInvocation invocation; 5         Object oldProxy = null; 6         boolean setProxyContext = false; 7  8         TargetSource targetSource = this.advised.targetSource; 9         Object target = null;10 11         try {12             if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {13                 // The target does not implement the equals(Object) method itself.14                 // 目标类体系中未实现equals方法,但是代理的目标方法却是equals方法15                 return equals(args[0]);16             }17             else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {18                 // The target does not implement the hashCode() method itself.19                 // 目标类体系中未实现hashCode方法,但是代理的目标方法却是hashCode方法20                 return hashCode();21             }22             else if (method.getDeclaringClass() == DecoratingProxy.class) {23                 // There is only getDecoratedClass() declared -> dispatch to proxy config.24                 return AopProxyUtils.ultimateTargetClass(this.advised);25             }26             // isAssignableFrom方法的意义:调用方如果是参数方的同类(接口)或者父类(接口),则返回true27             else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&28                     method.getDeclaringClass().isAssignableFrom(Advised.class)) {29                 // Service invocations on ProxyConfig with the proxy config...30                 // 直接反射调用目标方法31                 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);32             }33 34             Object retVal;35 36             if (this.advised.exposeProxy) {37                 // Make invocation available if necessary.38                 // 如果设置了exposeProxy=true,那么就代理保存起来备用39                 oldProxy = AopContext.setCurrentProxy(proxy);40                 setProxyContext = true;41             }42 43             // Get as late as possible to minimize the time we "own" the target,44             // in case it comes from a pool.45             target = targetSource.getTarget();46             Class<?> targetClass = (target != null ? target.getClass() : null);47 48             // Get the interception chain for this method.49             // 1-获取目标方法的拦截链50             List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);51 52             // Check whether we have any advice. If we don't, we can fallback on direct53             // reflective invocation of the target, and avoid creating a MethodInvocation.54             if (chain.isEmpty()) {55                 // We can skip creating a MethodInvocation: just invoke the target directly56                 // Note that the final invoker must be an InvokerInterceptor so we know it does57                 // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.58                 // 如果目标类没有拦截器链,则直接反射调用目标方法59                 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);60                 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);61             }62             else {63                 // We need to create a method invocation...64                 // 2-创建一个方法调用,并执行,ReflectiveMethodInvocation是Spring封装的65                 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);66                 // Proceed to the joinpoint through the interceptor chain.67                 // 3-执行拦截器链,在ReflectiveMethodInvocation中维护了拦截器调用的计数器,保证拦截器的逐个调用,完成所有拦截器调用之后会反射调用目标方法。68                 retVal = invocation.proceed();69             }70 71             // Massage return value if necessary.72             Class<?> returnType = method.getReturnType();73             if (retVal != null && retVal == target &&74                     returnType != Object.class && returnType.isInstance(proxy) &&75                     !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {76                 // Special case: it returned "this" and the return type of the method77                 // is type-compatible. Note that we can't help if the target sets78                 // a reference to itself in another returned object.79                 retVal = proxy;80             }81             else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {82                 throw new AopInvocationException(83                         "Null return value from advice does not match primitive return type for: " + method);84             }85             return retVal;86         }87         finally {88             if (target != null && !targetSource.isStatic()) {89                 // Must have come from TargetSource.90                 targetSource.releaseTarget(target);91             }92             if (setProxyContext) {93                 // Restore old proxy.94                 AopContext.setCurrentProxy(oldProxy);95             }96         }97     }

复制代码

  上面的源码并非发现织入的痕迹,让我们接着看看ReflectiveMethodInvocation类的proceed方法:

  源码26-来自:ReflectiveMethodInvocation

复制代码

 1 @Override 2     @Nullable 3     public Object proceed() throws Throwable { 4         // We start with an index of -1 and increment early. 5         // 完成所有增强之后执行目标切点方法 6         if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { 7             // 直接调用目标方法 8             return invokeJoinpoint(); 9         }10 11         // 获取下一个要执行的拦截器12         Object interceptorOrInterceptionAdvice =13                 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);14         if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {15             // Evaluate dynamic method matcher here: static part will already have16             // been evaluated and found to match.17             InterceptorAndDynamicMethodMatcher dm =18                     (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;19             if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {20                 // 动态匹配成功,则执行拦截器逻辑21                 return dm.interceptor.invoke(this);22             }23             else {24                 // Dynamic matching failed.25                 // Skip this interceptor and invoke the next in the chain.26                 // 动态匹配失败,跳过当前拦截器,递归执行下一个拦截器27                 return proceed();28             }29         }30         else {31             // It's an interceptor, so we just invoke it: The pointcut will have32             // been evaluated statically before this object was constructed.33             // 普通的拦截器,直接调用即可34             return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);35         }36     }

复制代码

  拦截器的调用执行invoke方法,并将this(当前实例)作为参数,来保证调用链的顺利执行,具体的逻辑那就在每个拦截器的invoke方法之中了,执行完拦截器的逻辑之后,就可以执行目标方法的逻辑了。

  这正是织入的实现。

  我们从织入的逻辑中并未发现有对拦截器执行顺序进行控制的逻辑,那么那些前置、后置、环绕、异常等的执行位置是怎么实现的呢?

3、织入实现原理

  虽然在ReflectiveMethodInvocation的proceed方法中看到目标方法是最后才被执行,那么那些后置、环绕、异常的通知是怎么实现的呢,如果我们打开各种通知实现的invoke方法中,就会发现一些东西:

  我们查看五种通知后发现

  源码27-来自:AspectJAfterReturningAdvice、AspectJAfterAdvice、AspectJAroundAdvice、AspectJMethodBeforeAdvice、AspectJAfterThrowingAdvice

复制代码

 1     // AspectJAfterReturningAdvice:后置通知 2     @Override 3     public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable { 4         if (shouldInvokeOnReturnValueOf(method, returnValue)) { 5             invokeAdviceMethod(getJoinPointMatch(), returnValue, null); 6         } 7     } 8  9     // AspectJAfterAdvice:后置终点通知10     @Override11     public Object invoke(MethodInvocation mi) throws Throwable {12         try {13             return mi.proceed();14         }15         finally {16             invokeAdviceMethod(getJoinPointMatch(), null, null);17         }18     }19 20     // AspectJAroundAdvice:环绕通知21     @Override22     public Object invoke(MethodInvocation mi) throws Throwable {23         if (!(mi instanceof ProxyMethodInvocation)) {24             throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);25         }26         ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;27         ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);28         JoinPointMatch jpm = getJoinPointMatch(pmi);29         return invokeAdviceMethod(pjp, jpm, null, null);30     }31 32     // AspectJMethodBeforeAdvice:前置通知33     @Override34     public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {35         invokeAdviceMethod(getJoinPointMatch(), null, null);36     }37 38     // AspectJAfterThrowingAdvice:异常通知39     @Override40     public Object invoke(MethodInvocation mi) throws Throwable {41         try {42             return mi.proceed();43         }44         catch (Throwable ex) {45             if (shouldInvokeOnThrowing(ex)) {46                 invokeAdviceMethod(getJoinPointMatch(), null, ex);47             }48             throw ex;49         }50     }

复制代码

  我们发现这五个通知里面的invoke方法中都调用了invokeAdviceMethod方法,这个方法是在AbstractAspectJAdvice抽象类中定义的。

  源码28-来自:AbstractAspectJAdvice

1     protected Object invokeAdviceMethod(2             @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)3             throws Throwable {4         // 执行参数绑定,然后使用参数调用通知方法5         return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));6     }

  此处有三个方法调用:

    getJoinPoint方法,用户获取当前的连接点实例

    argBinging方法,用于进行参数绑定操作

    invokeAdviceMethodWithGivenArgs方法执行通知方法

  首先看看getJointPoint方法:

  源码29-来自:AbstractAspectJAdvice

复制代码

 1     protected JoinPoint getJoinPoint() { 2         // 获取当前的连接点实例 3         return currentJoinPoint(); 4     } 5  6     public static JoinPoint currentJoinPoint() { 7         // 首先尝试从ExposeInvocationInterceptor拦截器中获取当前的方法调用 8         MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); 9         if (!(mi instanceof ProxyMethodInvocation)) {10             throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);11         }12         ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;13         // 然后从方法调用之中获取连接点实例jp14         JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);15         if (jp == null) {16             // 如果未获取到连接点实例,并设置到方法调用之中17             jp = new MethodInvocationProceedingJoinPoint(pmi);18             pmi.setUserAttribute(JOIN_POINT_KEY, jp);19         }20         return jp;21     }

复制代码

  上面逻辑很简单,就是从方法调用上下文中获取连接点,并返回,需要注意的是,此处一般情况是需要走if(jp == null)中的逻辑的,意思就是这里一般会是首次为方法调用设置连接点的地方,这也正是懒实例化的实现-在真正需要使用的时候才进行创建。

  下面看看源码28中第二个方法:参数绑定

  源码30-来自:AbstractAspectJAdvice

复制代码

 1     protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch, 2             @Nullable Object returnValue, @Nullable Throwable ex) { 3  4         // 提前估测参数绑定 5         // 最后就是将所有通知中候选的参数的名称和类型保存到了切点对应属性之中备用 6         calculateArgumentBindings(); 7  8         // AMC start 9         Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];10         int numBound = 0;11 12         if (this.joinPointArgumentIndex != -1) {13             adviceInvocationArgs[this.joinPointArgumentIndex] = jp;14             numBound++;15         }16         else if (this.joinPointStaticPartArgumentIndex != -1) {17             adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();18             numBound++;19         }20 21         if (!CollectionUtils.isEmpty(this.argumentBindings)) {22             // binding from pointcut match23             // 1-通过切点匹配进行参数绑定24             if (jpMatch != null) {25                 PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();26                 for (PointcutParameter parameter : parameterBindings) {27                     String name = parameter.getName();28                     Integer index = this.argumentBindings.get(name);29                     adviceInvocationArgs[index] = parameter.getBinding();30                     numBound++;31                 }32             }33             // binding from returning clause34             // 2-通过返回名称进行参数绑定35             if (this.returningName != null) {36                 Integer index = this.argumentBindings.get(this.returningName);37                 adviceInvocationArgs[index] = returnValue;38                 numBound++;39             }40             // binding from thrown exception41             // 3-通过异常返回进行参数绑定42             if (this.throwingName != null) {43                 Integer index = this.argumentBindings.get(this.throwingName);44                 adviceInvocationArgs[index] = ex;45                 numBound++;46             }47         }48 49         if (numBound != this.parameterTypes.length) {50             throw new IllegalStateException("Required to bind " + this.parameterTypes.length +51                     " arguments, but only bound " + numBound + " (JoinPointMatch " +52                     (jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");53         }54         55         // 这里会将通知方法中的所有参数进行绑定,因为他们都是候选者,除了一些特殊的不需绑定的之外(只三种切点类型)56         return adviceInvocationArgs;57     }

复制代码

  参数绑定的重点在于第一步,估测参数绑定,这一步会将所有通知中候选的参数的名称和类型保存到了切点对应属性之中备用,我们来看看:

  源码31-来自AbstractAspectJAdvice

复制代码

 1     public final synchronized void calculateArgumentBindings() { 2         // The simple case... nothing to bind. 3         if (this.argumentsIntrospected || this.parameterTypes.length == 0) { 4             // 无参可绑的情况,直接返回 5             return; 6         } 7  8         // 获取参数类型的数量numUnboundArgs(未绑参数数量) 9         int numUnboundArgs = this.parameterTypes.length;10         Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();11         // 排除parameterTypes中JoinPoint类型、ProceedingJoinPoint类型、JoinPoint.StaticPart类型的参数12         // JoinPoint类型的参数可作为非环绕通知的首个参数13         // ProceedingJoinPoint类型的参数可作为环绕通知的首个参数14         // JoinPoint.StaticPart类型的参数也可以作为某些通知的首个参数15         // 谨记,以上三种类型的参数只能作为对应通知的首个参数,当然也可以和其他参数共存,但位置必须位于首个,原因也很简单,因为此处判断的时候完全就是在拿首个参数类型来完成的。16         // 这三个参数是可以直接使用的,无需进行参数绑定操作,所以在这里排除掉17         if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||18                 maybeBindJoinPointStaticPart(parameterTypes[0])) {19             // 上述三种参数不需要绑定20             numUnboundArgs--;21         }22 23         if (numUnboundArgs > 0) {24             // need to bind arguments by name as returned from the pointcut match25             // 排除以上类型之后,如果还有剩余,则需要根据从切入点匹配返回的名称绑定参数26             // 我们要明白:切点表达式完全就是用来匹配用的,哪怕其中有参数,也是为了匹配指定参数用的,他不带有任何传参功能,传参功能只有通知方法才有27             // 所以这里的剩余参数个数,其实就是通知方法剩余参数,这里是依据参数名称来进行参数绑定28             bindArgumentsByName(numUnboundArgs);29         }30 31         this.argumentsIntrospected = true;32     }

复制代码

  这个方法中主要就是排除了三大类位于首位的切点参数类型,这三类型参数不需要进行绑定。然后对剩余的参数进行绑定操作:

  源码32-来自AbstractAspectJAdvice

复制代码

 1     // 通过name来绑定参数 2     private void bindArgumentsByName(int numArgumentsExpectingToBind) { 3         if (this.argumentNames == null) { 4             // 创建参数名称发现器,并获取指定通知方法的参数名称 5             this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod); 6         } 7         if (this.argumentNames != null) { 8             // We have been able to determine the arg names. 9             // 只要确认通知使用有参数的就行10             bindExplicitArguments(numArgumentsExpectingToBind);11     }12         else {13             throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +14                     "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +15                     "the argument names were not specified and could not be discovered.");16         }17     }

复制代码

  这一步,我们需要先创建参数名称发现器,然后发现器来获取当前通知方法的参数名的数组argumentNames。

  这个数组的作用仅仅是用来判空,只要其有值,即通知方法有参数,那么就需要执行绑定操作。

  首先来看看创建发现器的源码:

  源码33-来自AbstractAspectJAdvice

复制代码

 1     protected ParameterNameDiscoverer createParameterNameDiscoverer() { 2         // We need to discover them, or if that fails, guess, 3         // and if we can't guess with 100% accuracy, fail. 4         // DefaultParameterNameDiscoverer是参数名称发现器的默认实现,他其实是一个 5         DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); 6         AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer = 7                 new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression());// 切点的表达式 8         // 如果返回通知后绑定返回值,则returningName为非null 9         adviceParameterNameDiscoverer.setReturningName(this.returningName);10         // 如果在抛出通知后绑定了抛出的值,则throwingName为非null11         adviceParameterNameDiscoverer.setThrowingName(this.throwingName);12         // Last in chain, so if we're called and we fail, that's bad...13         // 设置在未能推导出通知参数名称的情况下是否抛出IllegalArgumentException和AmbiguousBindingException异常14         adviceParameterNameDiscoverer.setRaiseExceptions(true);15         // 将配置好的发现器添加到DefaultParameterNameDiscoverer中并返回16         discoverer.addDiscoverer(adviceParameterNameDiscoverer);17         return discoverer;18     }

复制代码

  源码中AspectJAdviceParameterNameDiscoverer为真正执行发现操作的发现器。

  源码34-来自AspectJAdviceParameterNameDiscoverer

复制代码

  1     @Override  2     @Nullable  3     public String[] getParameterNames(Method method) {  4         // 参数类型  5         this.argumentTypes = method.getParameterTypes();  6         // 初始化未绑定参数个数  7         this.numberOfRemainingUnboundArguments = this.argumentTypes.length;  8         // 初始化已绑定参数名称数组  9         this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments]; 10  11         int minimumNumberUnboundArgs = 0;// 初始化最少未绑参数个数 12         // 针对后置通知和异常通知进行特殊处理,需要将返回值进行绑定 13         if (this.returningName != null) { 14             // 如果通知类型为后置通知 15             minimumNumberUnboundArgs++; 16         } 17         if (this.throwingName != null) { 18             // 如果通知类型为异常通知 19             minimumNumberUnboundArgs++; 20         } 21         if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) { 22             throw new IllegalStateException( 23                     "Not enough arguments in method to satisfy binding of returning and throwing variables"); 24         } 25  26         try { 27             //分成八步进行操作 28             int algorithmicStep = STEP_JOIN_POINT_BINDING; 29             while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) { 30                 switch (algorithmicStep++) { 31                     case STEP_JOIN_POINT_BINDING: 32                         // 1-连接点参数绑定 33                         // 连接点参数绑定格式为:thisJoinPoint -> 0 34                         if (!maybeBindThisJoinPoint()) { 35                             // 连接点参数绑定格式为:thisJoinPointStaticPart -> 0 36                             maybeBindThisJoinPointStaticPart(); 37                         } 38                         break; 39                     case STEP_THROWING_BINDING: 40                         // 2-异常返回参数绑定 41                         // 异常参数绑定格式为:throwingName -> throwableIndex 42                         // throwableIndex为通知参数列表中接收异常的参数的位置 43                         maybeBindThrowingVariable(); 44                         break; 45                     case STEP_ANNOTATION_BINDING: 46                         // 3-注解参数绑定 47                         // 格式:varName -> annotationIndex 48                         maybeBindAnnotationsFromPointcutExpression(); 49                         break; 50                     case STEP_RETURNING_BINDING: 51                         // 4-返回参数绑定 52                         // 绑定返回值时,只有在未绑定参数只剩余1个的情况下才能绑定,否则不予绑定 53                         // 当只剩余一个未绑定的情况下,将返回值与剩余的那个位置的下标进行绑定即可 54                         maybeBindReturningVariable(); 55                         break; 56                     case STEP_PRIMITIVE_ARGS_BINDING: 57                         // 5-原始类型参数绑定 58                         // 只有在有1个原始类型参数,且只有一个候选位置时才执行绑定操作 59                         maybeBindPrimitiveArgsFromPointcutExpression(); 60                         break; 61                     case STEP_THIS_TARGET_ARGS_BINDING: 62                         // 6-切点表达式参数绑定 63                         // 只有只存在一个变量名称的时候才能执行绑定 64                         maybeBindThisOrTargetOrArgsFromPointcutExpression(); 65                         break; 66                     case STEP_REFERENCE_PCUT_BINDING: 67                         // 7-引用切点绑定 68                         // 只有只存在一个变量名称的时候才能执行绑定 69                         maybeBindReferencePointcutParameter(); 70                         break; 71                     default: 72                         throw new IllegalStateException("Unknown algorithmic step: " + (algorithmicStep - 1)); 73                 } 74             } 75         } 76         catch (AmbiguousBindingException ambigEx) { 77             if (this.raiseExceptions) { 78                 throw ambigEx; 79             } 80             else { 81                 return null; 82             } 83         } 84         catch (IllegalArgumentException ex) { 85             if (this.raiseExceptions) { 86                 throw ex; 87             } 88             else { 89                 return null; 90             } 91         } 92  93         if (this.numberOfRemainingUnboundArguments == 0) { 94             return this.parameterNameBindings; 95         } 96         else { 97             if (this.raiseExceptions) { 98                 throw new IllegalStateException("Failed to bind all argument names: " + 99                         this.numberOfRemainingUnboundArguments + " argument(s) could not be bound");100             }101             else {102                 // convention for failing is to return null, allowing participation in a chain of responsibility103                 return null;104             }105         }106     }

复制代码

  源码中针对各种情况进行了参数绑定操作,采用switch...case链式结构完成功能。虽然这一步最终目的是返回parameterNameBindings值,而且在源码32中看起来只是用于判空,好像用处不大,其实大错特错,这个parameterNameBindings在这里绑定完成之后,在以后会有大用,判空只是它的一个小功能罢了。

  至于针对每种参数类型的绑定逻辑就不再细究了。下面看看源码32中bindExplicitArguments方法

   源码35-来自:AbstractAspectJAdvice

复制代码

 1     private void bindExplicitArguments(int numArgumentsLeftToBind) { 2         Assert.state(this.argumentNames != null, "No argument names available"); 3         this.argumentBindings = new HashMap<>(); 4  5         // 获取通知方法的参数个数 6         int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount(); 7         if (this.argumentNames.length != numExpectedArgumentNames) { 8             // 参数个数不匹配 9             throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames +10                     " arguments to bind by name in advice, but actually found " +11                     this.argumentNames.length + " arguments.");12         }13 14         // So we match in number...15         // 获取parameterTypes中剩余的参数偏移量,将其绑定到argumentBindings中,numArgumentsLeftToBind正是之前排除三大首位切点参数之后的剩余参数量16         int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;17         for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {18             // 参数绑定的格式:name-> index19             this.argumentBindings.put(this.argumentNames[i], i);20         }21 22         // Check that returning and throwing were in the argument names list if23         // specified, and find the discovered argument types.24         if (this.returningName != null) {25             if (!this.argumentBindings.containsKey(this.returningName)) {26                 throw new IllegalStateException("Returning argument name '" + this.returningName +27                         "' was not bound in advice arguments");28             }29             else {30                 // 获取其对应的index值,将其另外绑定到discoveredReturningType和discoveredReturningGenericType中31                 Integer index = this.argumentBindings.get(this.returningName);32                 this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];33                 this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index];34             }35         }36         if (this.throwingName != null) {37             if (!this.argumentBindings.containsKey(this.throwingName)) {38                 throw new IllegalStateException("Throwing argument name '" + this.throwingName +39                         "' was not bound in advice arguments");40             }41             else {42                 // 获取其对应的index值,并将其另外绑定到discoveredThrowingType中43                 Integer index = this.argumentBindings.get(this.throwingName);44                 this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];45             }46         }47 48         // configure the pointcut expression accordingly.49         // 相应地配置切入点表达式。50         configurePointcutParameters(this.argumentNames, argumentIndexOffset);51     }

复制代码

  源码36-来自:AbstractAspectJAdvice

复制代码

 1     private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) { 2         int numParametersToRemove = argumentIndexOffset; 3         if (this.returningName != null) { 4             numParametersToRemove++; 5         } 6         if (this.throwingName != null) { 7             numParametersToRemove++; 8         } 9         // 之前将所有要移除的参数数量累加出来,需要移除首位的三种切点参数、returningName参数和throwingName参数10         // 剩余的参数就是切点表达式中可以出现的候选参数,这里初始化一个切点参数名数组pointcutParameterNames,将这些参数囊括进来11         // 另外再初始化两个数组,分别用于存放这些剩余参数的参数类型(pointcutParameterTypes)和通知方法所有参数类型(methodParameterTypes)12         String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];13         Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];14         Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();15 16         int index = 0;17         for (int i = 0; i < argumentNames.length; i++) {18             if (i < argumentIndexOffset) {19                 // 小于偏移量的参数为移除的三大切点类型参数20                 continue;21             }22             if (argumentNames[i].equals(this.returningName) ||23                 argumentNames[i].equals(this.throwingName)) {24                 // 这里在对returningName和throwingName参数进行排除25                 continue;26             }27             // 剩余的就是通知中的切点表达式候选参数,将这些参数的参数名逐个保存到切点参数名数组中28             // 将这些参数的参数类型逐个保存到切点参数类型数组中29             pointcutParameterNames[index] = argumentNames[i];30             pointcutParameterTypes[index] = methodParameterTypes[i];31             index++;32         }33 34         // 然后,分别将两个数组设置到切点的对应属性中35         this.pointcut.setParameterNames(pointcutParameterNames);// 設置参数名称36         this.pointcut.setParameterTypes(pointcutParameterTypes);// 设置参数类型37     }

复制代码

  到此为止我们看到了参数绑定操作的最终结果,将除了首位三大切点类型参数、returningName参数和throwingName参数之外的通知的剩余参数都作为候选参数,将其名称和类型分别放置到一个数组中,再设置到切点之中。

  逻辑到这里我们可以回到源码30中,执行剩余的逻辑,将所有参数迁移到adviceInvocationArgs中进行绑定,绑定的逻辑还是一样,都是以数组的方式保存。

  然后再回到源码28中,执行invokeAdviceMethodWithGivenArgs方法

复制代码

 1     protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { 2         Object[] actualArgs = args; 3         if (this.aspectJAdviceMethod.getParameterCount() == 0) { 4             actualArgs = null; 5         } 6         try { 7             ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); 8             // TODO AopUtils.invokeJoinpointUsingReflection 9             return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);10         }11         catch (IllegalArgumentException ex) {12             throw new AopInvocationException("Mismatch on arguments to advice method [" +13                     this.aspectJAdviceMethod + "]; pointcut expression [" +14                     this.pointcut.getPointcutExpression() + "]", ex);15         }16         catch (InvocationTargetException ex) {17             throw ex.getTargetException();18         }19     }

复制代码

  逻辑很简单,就是依据给定的参数来反射调用通知的方法逻辑。

至此我们分析完JDK动态代理实现AOP切面编程的整个逻辑。

 

原文出处:https://www.cnblogs.com/V1haoge/p/9560803.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消