Spring AOP高级——源码实现(3)AopProxy代理对象之JDK动态代理的创建过程
spring-aop-4.3.7.RELEASE
在《Spring AOP高级——源码实现(1)动态代理技术》中介绍了两种动态代理技术,当然在Spring AOP中代理对象的生成也是运用的这两种技术。本文将介绍Spring AOP如何通过JDK动态代理的方式创建代理对象。
JDK动态代理以及CGLIB代理这两种生成代理对象的方式在Spring AOP中分别对应两个类:JdkDynamicAopProxy和CglibAopProxy,而AopProxy是这两个类的父接口。
AopProxy接口中定义了两个基本方法:
1 public interface AopProxy { 2 /** 3 *使用默认的类加载器生成代理对象,默认的类加载器通常是当前线程 4 *的上下文类加载器,可通过Thread#getContextClassLoader()获得 5 */ 6 Object getProxy(); 7 /** 8 * 使用指定的类加载器创建代理对象,通常用于比较低级别的代理对象 9 * 创建,至于什么时候用这个暂时先放一放 10 */ 11 Object getProxy(ClassLoader classLoader); 12 }
接着来看它的实现——JdkDynamicAopProxy。
1 //①首先查看JdkDynamicAopProxy类的成员变量 2 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { //JDK动态代理需要实现 InvocationHandler类 3 4 private static final long serialVersionUID = 5531744639992436476L; 5 6 private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); 7 8 /** 代理类的相关配置,这个类继承自ProxyConfig,都是代理类的相关配置*/ 9 private final AdvisedSupport advised; 10 11 /** 12 * 用于判断目标对象实现的接口是否定义了equals方法 13 */ 14 private boolean equalsDefined; 15 16 /** 17 * 用于判断目标对象实现的接口是否定义了hashCode方法 18 */ 19 private boolean hashCodeDefined;
类中有两个成员变量需要额外注意一下,一个是第14行的equalsDefined变量和第19行的hashCodeDefined变量。
讨论它们之前需要首先明确一点,通常情况下,Spring AOP代理对象不会对equals和hashCode方法增强,注意这是在通常情况下,那什么是“通常情况”,什么又是“不通常情况”呢?
如果目标对象直接重写Object对象的equals或hashCode方法,此时Spring AOP则不会对它增强,equalsDefined=false或hashCodeDefined=false;如果目标对象实现的接口定义了equals或hashCode方法,此时Spring AOP则会对它增强,equalsDefined=true或hashCodeDefined=true。所以“通常情况”就是我们并不会在接口定义equals或hashCode方法,“不通常情况”就是在有的特殊情况下在接口定义equals或hashCode方法。再换句话说,如果我们需要Spring AOP增强equals或hashCode方法则必须要在其目标对象的实现接口定义equals或hashCode方法。
1 //②再来看看JdkDynamicAopProxy类的构造方法 2 public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { //只有一个带AdvisedSupport类型的构造方法,这个类型上面提到过是生成代理类的相关配置,必须不能为空,否则将抛出参数异常的错误 3 Assert.notNull(config, "AdvisedSupport must not be null"); 4 if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { //AdvisedSupport配置类中需要定义通知器和目标源。 5 throw new AopConfigException("No advisors and no TargetSource specified"); 6 } 7 this.advised = config; //赋值给成员变量 8 }
构造方法中对于参数的校验有个比较特殊地方,构造方法不仅需要判断参数不能为空,而且要判断参数中的两个变量——advisors通知器和targetSource目标源。
通知器在《Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)》中讲解过它是一个特殊的切面,而targetSource目标则是为了获取target目标对象,这两个都是实现AOP的重要组成部分必然不能为空。
1 //③接下来是生成代理对象的getProxy方法 2 @Override 3 public Object getProxy() { 4 return getProxy(ClassUtils.getDefaultClassLoader()); //没有指定ClassLoader则通过Thread.currentThread.getContextClassLoader()获取当前线程上下文的类加载器。 5 } 6 7 @Override 8 public Object getProxy(ClassLoader classLoader) { 9 if (logger.isDebugEnabled()) { 10 logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 11 } 12 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); //获取代理对象需要实现的完整接口 13 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); //这里就是上面提到过的判断的目标对象实现的接口是否定义了equals或hashCode方法,具体原因不再展开 14 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //通过JDK生成代理对象。第一个ClassLoader代表创建类的类加载器,第二个表示需要被代理的目标对象的接口,第三个参数InvocationHandler叫做调用处理器,在这里它就是对象本身,调用的代理对象方法实际就是调用InvocationHandler接口中的invoke方法。 15 }
第12行调用了AopProxyUtils.completeProxiedInterfaces(this.advised, true)方法,传入了Spring AOP代理对象配置对象,第二个参数表示是否暴露DecoratingProxy接口,如果设置为true则代理对象会实现DecoratingProxy接口,这个方法是在Spring4.3后新增的方法。来看看这个方法的实现。
1 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { 2 Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); //获取目标对象实现的接口 3 …//省略的代码是通过AdviceSupport没有获取到目标对象的实现接口时,则通过直接通过target目标对象来获取 4 boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);//是否新增SpringProxy,在AdvisedSupport#isInterfaceProxied方法中会判断传入的接口是否已经由目标对象实现。此处传入SpringProxy.class判断目标对象是否已经实现该接口,如果没有实现则在代理对象中需要新增SpringProxy,如果实现了则不必新增。 5 boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); //是否新增Adviced接口,注意不是Advice通知接口。ProxyConfig#isOpaque方法用于返回由这个配置创建的代理对象是否应该避免被强制转换为Advised类型。还有一个条件和上面的方法一样,同理,传入Advised.class判断目标对象是否已经实现该接口,如果没有实现则在代理对象中需要新增Advised,如果实现了则不必新增。 6 boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); //是否新增DecoratingProxy接口,同样的判断条件有两个,第一个参数decoratingProxy,在调用completeProxiedInterfaces方法时传入的是true,第二个判断条件和上面一样判断被代理的目标对象是否已经实现了DecoratingProxy接口。通常情况下这个接口也会被加入到代理对象中,这是Spring4.3新增的。 7 int nonUserIfcCount = 0; 8 if (addSpringProxy) { 9 nonUserIfcCount++; 10 } 11 if (addAdvised) { 12 nonUserIfcCount++; 13 } 14 if (addDecoratingProxy) { 15 nonUserIfcCount++; 16 } 17 Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; //代理类的接口一共是目标对象的接口+上面三个接口SpringProxy、Advised、DecoratingProxy 18 System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); //将目标对象的接口拷贝,这个方法在《System.arraycopy(src, srcPos, dest, destPos, length) 与 Arrays.copyOf(original, newLength)区别》有介绍 19 //下面就是讲那三个接口加入到specifiedInterfaces数组中并返回 20 int index = specifiedInterfaces.length; 21 if (addSpringProxy) { 22 proxiedInterfaces[index] = SpringProxy.class; 23 index++; 24 } 25 if (addAdvised) { 26 proxiedInterfaces[index] = Advised.class; 27 index++; 28 } 29 if (addDecoratingProxy) { 30 proxiedInterfaces[index] = DecoratingProxy.class; 31 } 32 return proxiedInterfaces; 33 }
生成代理对象后,下一步当然就是调用代理方法,当然这就包括了调用AOP的增强方法以及目标对象的方法。
JDK动态代理生成的代理对象需要实现InvocationHandler接口和invoke方法,这个invoke方法就是JDK代理对象进行拦截的回调入口。 JdkDynamicAopProxy就实现了InvocationHandler接口,这个接口只有一个方法invoke。
public Object invoke(Object proxy, Method method, Object[] args)
这个方法有三个参数:
proxy:指的是我们所代理的那个真实的对象;
method:指的是我们所代理的那个真实对象的某个方法的Method对象;
args:指的是调用那个真实对象方法的参数。
接下来一步一步查看JdkDynamicAopProxy是如何实现invoke方法的。
1 //⑤代理对象的回调方法 2 @Override 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 Class<?> targetClass = null; 10 Object target = null; 11 12 try { 13 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { 14 // 这里就是之前提到过的,“通常情况”Spring AOP不会对equals方法进行拦截增强,所以这里判断如果目标对象没有定义equals方法的话,就会直接调用而不会增强 15 return equals(args[0]); 16 } 17 else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { 18 //同上 19 return hashCode(); 20 } 21 else if (method.getDeclaringClass() == DecoratingProxy.class) { 22 // 这里是上面的疑点,也是Spring4.3新出现的特性 23 return AopProxyUtils.ultimateTargetClass(this.advised); 24 } 25 else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && 26 method.getDeclaringClass().isAssignableFrom(Advised.class)) { 27 //这个地方就有点意思了,Spring AOP不会增强直接实现Advised接口的目标对象,再重复一次,也就是说如果目标对象实现的Advised接口,则不会对其应用切面进行方法的增强。 28 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); //这个方法是一个对Java通过反射调用方法的封装 29 } 30 Object retVal; //方法的返回值 31 if (this.advised.exposeProxy) { //是否暴露代理对象,默认false可配置为true,如果暴露就意味着允许在线程内共享代理对象,注意这是在线程内,也就是说同一线程的任意地方都能通过AopContext获取该代理对象,这应该算是比较高级一点的用法了。 32 oldProxy = AopContext.setCurrentProxy(proxy); 33 setProxyContext = true; 34 } 35 target = targetSource.getTarget(); //通过目标源获取目标对象 36 if (target != null) { 37 targetClass = target.getClass(); //获取目标对象Class对象 38 } 39 //...暂略 40 }
上面可以说是对方法调用的一些预处理,有几个重要的地方:
- 第13-29行,对一些情况的特殊判断,主要是不对目标对象应用切面;
- 第31行,判断是否暴露代理对象,默认false不暴露,如果暴露则表示在线程内的任意位置都能通过AopContext获取代理对象;
- 第35行,通过目标源获取目标对象。
下面可以说是重中之重,也就是具体是如何对目标对象应用切面对其方法进行增强的。
1 //⑥获取拦截器链,并调用增强方法及目标对象的方法 2 @Override 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 //... 5 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //这个方法获取拦截器链。 6 if (chain.isEmpty()) { //拦截器链如果为空的话就直接调用目标对象的方法。. 7 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); 8 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); //直接调用目标对象的方法。 9 } 10 else { //通过ReflectiveMethodInvocation.proceed调用拦截器中的方法和目标对象方法 11 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //ReflectiveMethodInvocation对象完成对AOP功能实现的封装 12 retVal = invocation.proceed(); //获取返回值 13 } 14 Class<?> returnType = method.getReturnType(); //获取返回值类型 15 if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { 16 // 一些列的判断条件,如果返回值不为空,且为目标对象的话,就直接将目标对象赋值给retVal 17 retVal = proxy; 18 } 19 else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { 20 throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); //没有返回值 21 } 22 return retVal; 23 } 24 //...暂略
这段代码有3个比较关键的地方:
- 第5行,获取拦截器链,相当于是获取潜在的增强方法,只是潜在,后续还有匹配的判断;
- 第8行,没有获取到拦截器链,此时相当于直接调用目标对象的方法,AopUtils.invokeJoinpointUsingReflection方法实际是对JDK反射调用方法的一个封装;
- 第12行,这里就是进入拦截器链中增强方法和目标对象的调用的地方,关键。
接下来就是进入ReflectiveMethodInvocation.proceed方法,来探讨下Spring AOP是如何对目标对象调用方法进行增强以及调用的。
1 //⑦连接器链的调用,ReflectiveMethodInvocation#proceed,这是一个递归方法,退出条件就是调用完了拦截链中的所有拦截器方法后,再调用目标对象的方法。 2 这个方法的逻辑在于通过拦截器链,逐个获取其中的拦截器,再通过匹配判断,判断是否适用,如果适用则取出拦截器中的通知器并通过通知器的invoke方法调用,如果不适用则递归调用。 3 @Override 4 public Object proceed() throws Throwable { 5 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //调用完了所有的拦截链中拦截器的增强方法,直接调用目标对象的方法并退出 6 return invokeJoinpoint(); //这个方法就是调用AopUtils.invokeJoinpointUsingReflection,上面提到过。 7 } 8 9 Object interceptorOrInterceptionAdvice = 10 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //从拦截器链中获取拦截器 11 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { //这里进行动态匹配 12 InterceptorAndDynamicMethodMatcher dm = 13 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; 14 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //这里如果和定义的切点匹配,那么这个通知就会得到执行 15 return dm.interceptor.invoke(this); 16 } 17 else { 18 return proceed(); //不适用递归继续获取拦截器进行匹配、判断、调用 19 } 20 } 21 else { //这里判断出这个拦截器是一个MethodInterceptor则直接调用 22 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); //动态匹配失败则直接调用 23 } 24 }
这里就是整个调用过程,同样有一个大的重点:
第11行,这里是进行动态匹配,什么是动态匹配呢?我们给出如下的示例,按照xml配置一个通知器而不是切面。
1 <!--定义通知器bean,需要实现Advice接口--> 2 <bean id="testAdvisor" class="com.springdemo.aop.MyAdvice"/> 3 <!--目标对象--> 4 <bean id="testPoint" class="com.springdemo.aop.TestPoint"/> 5 <!--advisor通知器--> 6 <bean id="testAop" class="org.springframework.aop.framework.ProxyFactoryBean"> 7 <!--目标对象实现的接口--> 8 <property name="proxyInterfaces"> 9 <value>com.springdemo.aop.Test</value> 10 </property> 11 <!--拦截器,也就是上面定义的通知器beanId--> 12 <property name="interceptorNames"> 13 <list> 14 <value>testAdvisor</value> 15 </list> 16 </property> 17 <!--目标对象,也就是上面定义的目标对象beanId--> 18 <property name="targetName"> 19 <value>testPoint</value> 20 </property> 21 </bean>
上面的注释已经比较清楚了,如果我们按照这样的方式来实现一个目标对象的方法增强,那么此时,在调用代理对象的方法时,也就是在执行上面ReflectiveMethodInvocation.proceed方法时是不会进行动态匹配的,因为我们在定义一个advisor通知器也就是上面的Myadvice类的时候是一定会实现Advice接口,如果我们定义的是MethodBeforeAdvice那么此时就已经确定一定会是前置通知。所以它一定会进入else那个分支去。
如果,我们不是通过定义advisor通知器的方式,而是直接定义一个切面,那么,在我们定义切面这个类是是不需要实现任何接口的,其中的任意方法都可以作为前置或者后置通知,这取决于你的xml配置,例如下面的配置:
<aop:config> <aop:aspect ref="aspectTest"> <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/> <aop:before method="doBefore" pointcut-ref="test"/> <aop:after-returning method="doAfter" pointcut-ref="test"/> </aop:aspect> </aop:config>
关于这个地方的理解,建议多写几个demo通过打断点的方式反复咀嚼。下一篇将会介绍Spring AOP是如何通过CGLib生成代理对象的,本文还有很多很多不足之处,希望有看到的人懂的不懂的都能指出来,非常非常感谢。
这是一个能给程序员加buff的公众号